npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

eve-channel-sendblue

v0.4.0

Published

Sendblue (iMessage/SMS) channel for the eve agent framework

Readme

eve-channel-sendblue

Sendblue (iMessage/SMS) channel for the eve agent framework. Inbound texts arrive as a webhook and start or resume an eve session; the agent's reply is sent back over the same line.

Install

bun add eve-channel-sendblue
# peer deps: eve, ai

Usage

Add a channel file to your eve app:

import { sendblueChannel } from "eve-channel-sendblue";

export default sendblueChannel();

Credentials fall back to environment variables, so the call can stay empty once they are set (a webhook secret is required by default, see Webhook security):

| Variable | Purpose | | ------------------------------ | ------------------------------------------------------------------- | | SENDBLUE_API_KEY | Sendblue API key id | | SENDBLUE_API_SECRET | Sendblue API secret | | SENDBLUE_FROM_NUMBER | Default sender, E.164 (e.g. +14155551234) | | SENDBLUE_WEBHOOK_SECRET | Required by default; checked against the sb-signing-secret header | | SENDBLUE_STATUS_CALLBACK_URL | Optional; per-message delivery-status callback URL |

Or pass them explicitly. Each credential also accepts a lazy resolver function that is called once and cached:

export default sendblueChannel({
  credentials: {
    apiKey: process.env.SENDBLUE_API_KEY,
    apiSecret: () => secrets.get("sendblue-api-secret"), // sync or async resolver
    webhookSecret: process.env.SENDBLUE_WEBHOOK_SECRET,
  },
  fromNumber: "+14155551234",
  allowFrom: ["+15557654321"], // "*", a list, or an async resolver
  allowedServices: ["iMessage", "SMS"], // defaults to ["iMessage"]
});

With no credentials resolvable, the channel runs in dry-run mode: inbound webhooks drive a real eve session, but outbound sends are logged instead of delivered.

Webhooks

The channel mounts a single route:

POST /eve/v1/sendblue/webhook

Point your Sendblue message, status, and typing webhooks at that URL. The handler distinguishes the payload types internally.

Webhook security (fail closed by default)

The webhook is unauthenticated unless you verify it, so the channel requires a secret by default and throws on construction if none is configured. Set SENDBLUE_WEBHOOK_SECRET (or credentials.webhookSecret) and the same secret in Sendblue, so it sends the sb-signing-secret header. The header is compared in constant time, and requests without a valid signature get a 401.

To run the webhook open on purpose (local dev, a trusted network), set requireWebhookSecret: false. You then must not also provide a secret; that contradiction throws, so there's no ambiguity about what you meant.

sendblueChannel({ requireWebhookSecret: false }); // open, no secret

Features

  • Inbound + outbound iMessage and SMS, 1:1 and group threads.
  • Markdown flattening so replies read natively on iMessage.
  • Inbound media surfaced to the model by URL (see the note below).
  • Tapback reactions, typing indicators, and read receipts via the thread handle exposed to event handlers.
  • Proactive sends through the channel's receive() hook (schedules or cross-channel hand-off).
  • Sender allow-list, service filtering, and shared-secret webhook verification.

Continuation tokens

The channel-local raw token addresses a session by line and contact:

<fromNumber>:<contactNumber>     // 1:1
<fromNumber>:g:<groupId>         // group

sendblueContinuationToken(from, { contactNumber }) and decodeContinuationToken are exported for building tokens (for example, to start a proactive session).

Inbound media

Inbound attachments are passed to the model provider by URL, not staged into the eve sandbox. Staging wrote a durable eve-sandbox: file reference into session history whose bytes are gone on the next (fresh-sandbox) invocation, so a later turn failed the staging invariant and terminated the whole session. Passing the URL keeps a durable reference the provider fetches at model-call time.

Consequence: the model can only "see" an image while its URL is still fetchable, and eve re-references that URL on every later turn. Sendblue's inbound media_urls expire after ~30 days, so images drop out of long-lived conversations.

To make media durable, pass a persistMedia hook. On receipt the channel calls it with the attachment and uses the URL it returns (a permanent one from your own store) instead of Sendblue's. If the hook throws or returns nothing, the channel falls back to the Sendblue URL, so media can never fail a turn.

import { put } from "@vercel/blob";

sendblueChannel({
  async persistMedia({ url, mediaType, messageHandle }) {
    const res = await fetch(url);
    if (!res.ok) return url; // keep the Sendblue URL on failure
    const blob = await put(`sendblue/${messageHandle}`, await res.arrayBuffer(), {
      access: "public", // Sendblue media is already public/unguessable
      contentType: mediaType,
      addRandomSuffix: true,
    });
    return blob.url; // permanent, no ~30-day expiry
  },
});

Public Blob URLs never expire, so this extends media lifetime from ~30 days to permanent. For a private store, keep a stable id in history and mint short-lived signed URLs on demand from a tool, since a signed URL placed directly in history would expire while eve still references it.

Platform limitations

Inherited from Sendblue and iMessage: no message editing, no true unsend, tapbacks can be added but not removed via API, inbound media_urls expire after ~30 days, and typing indicators work for 1:1 chats only.

License

MIT