Sendblue

iMessage, SMS, and RCS adapter for Chat SDK, built and maintained by Sendblue. Tapbacks, typing indicators, delivery status callbacks, and number lookup.

Vendor-official adapter maintained by Sendblue, not Vercel or Chat SDK contributors. For feature requests, bug reports, and support, file an issue on the adapter's repo.

The Sendblue adapter connects Chat SDK bots to iMessage, SMS, and RCS. Use it when you want one bot implementation to handle Sendblue conversations alongside Slack, Teams, Discord, Telegram, email, or other Chat SDK adapters.

Sendblue maintains this adapter as Chat SDK and the Sendblue API evolve. For account, delivery, or credential help, contact Sendblue support.

Install

pnpm add chat-adapter-sendblue chat @chat-adapter/state-memory

The adapter uses the official Sendblue TypeScript SDK under the hood. For production, use a persistent state adapter such as @chat-adapter/state-redis instead of in-memory state.

Quick start

lib/bot.ts
import { Chat } from "chat";
import { createSendblueAdapter } from "chat-adapter-sendblue";
import { createMemoryState } from "@chat-adapter/state-memory";

const chat = new Chat({
  userName: "imessage-bot",
  adapters: {
    sendblue: createSendblueAdapter(),
  },
  state: createMemoryState(),
});

chat.onDirectMessage(async (thread, message) => {
  await thread.post(`Got it: ${message.text}`);
});

createSendblueAdapter() reads credentials from environment variables by default:

VariableRequiredDescription
SENDBLUE_API_KEYYesYour Sendblue API key ID
SENDBLUE_API_SECRETYesYour Sendblue API secret key
SENDBLUE_FROM_NUMBERYesYour Sendblue number in E.164 format
SENDBLUE_WEBHOOK_SECRETNoShared secret for webhook verification
SENDBLUE_STATUS_CALLBACK_URLNoStatus callback URL for outbound message delivery events

Find API credentials and assigned numbers in the Sendblue dashboard, or with the CLI:

npm install -g @sendblue/cli
sendblue setup
sendblue show-keys
sendblue lines

Or pass credentials explicitly:

createSendblueAdapter({
  apiKey: process.env.SENDBLUE_API_KEY,
  apiSecret: process.env.SENDBLUE_API_SECRET,
  defaultFromNumber: "+15551234567",
  webhookSecret: process.env.SENDBLUE_WEBHOOK_SECRET,
});

Webhooks

Create a webhook route and forward incoming Sendblue requests to Chat SDK:

app/api/webhooks/sendblue/route.ts
import { chat } from "@/lib/bot";

export async function POST(request: Request) {
  await chat.initialize();
  return chat.webhooks.sendblue(request);
}

Configure your Sendblue receive webhook to point at that endpoint, for example:

https://your-app.com/api/webhooks/sendblue

Webhook verification

If you set SENDBLUE_WEBHOOK_SECRET, Sendblue should send the same secret with webhook requests. The adapter verifies the sb-signing-secret header by default. Override the header name if needed:

createSendblueAdapter({
  webhookSecret: process.env.SENDBLUE_WEBHOOK_SECRET,
  webhookSecretHeader: "x-custom-header",
});

Sending messages

Use normal Chat SDK thread methods. Outbound messages go through Sendblue with iMessage-first delivery and automatic SMS/RCS fallback where available. Markdown is stripped to plain text because iMessage does not render it.

await thread.post("Hello from Sendblue via Chat SDK.");

Attachments

Inbound media URLs from Sendblue are parsed into Chat SDK attachment objects. To send media, use Sendblue's media_url parameter through the underlying SDK:

import type { SendblueAdapter } from "chat-adapter-sendblue";

const adapter = chat.getAdapter("sendblue") as SendblueAdapter;
const sdk = adapter.getSdk();

await sdk.messages.send({
  number: "+15551234567",
  from_number: process.env.SENDBLUE_FROM_NUMBER!,
  content: "Here is a photo.",
  media_url: "https://example.com/photo.jpg",
});

Reactions (tapbacks)

iMessage tapbacks are supported via addReaction. The adapter maps common emoji names to Sendblue's six tapback types:

TapbackAliases
loveheart
likethumbs_up, thumbsup, +1
dislikethumbs_down, thumbsdown, -1
laughhaha
emphasizeexclamation, !!
question?

Tapbacks can be added but not removed via the Sendblue API.

Typing indicators

startTyping() sends the animated typing bubble to the recipient. Supported for 1:1 conversations only, not group chats.

Message history

fetchMessages() retrieves conversation history from the Sendblue API with cursor-based pagination.

Number lookup

Check whether a phone number supports iMessage, SMS, or RCS:

const adapter = chat.getAdapter("sendblue") as SendblueAdapter;
const result = await adapter.evaluateService("+15551234567");
// { number: "+15551234567", service: "iMessage" }

Before high-volume sends, you can also use Sendblue's Lookup API to confirm iMessage support for a recipient.

Direct SDK access

For Sendblue APIs outside the standard Chat SDK adapter interface (media, contacts, webhooks, groups, and newer API features), use the underlying Sendblue SDK:

const adapter = chat.getAdapter("sendblue") as SendblueAdapter;
const sdk = adapter.getSdk();

await sdk.contacts.list();
await sdk.groups.sendMessage({ /* ... */ });
await sdk.webhooks.list();

Service filtering

By default, the adapter processes only inbound iMessage traffic. To also accept SMS and RCS:

createSendblueAdapter({
  allowedServices: ["iMessage", "SMS", "RCS"],
});

Thread ID format

Thread IDs encode the Sendblue line number and contact (or group) so conversations stay tied to a specific phone line:

sendblue:<from_base64url>:<contact_base64url>          # 1:1
sendblue:<from_base64url>:g:<group_id_base64url>       # group

Use encodeThreadId / decodeThreadId to work with them programmatically.

Production notes

  • Use a persistent Chat SDK state adapter in production so subscribed threads and conversation state survive deploys.
  • Store Sendblue API keys in environment variables or your host's secret manager.
  • Persist inbound media URLs if you need long-term access — Sendblue media URLs can expire.
  • Keep your from_number stable per contact so end users always see the same Sendblue line.

Limitations

  • No message editing — iMessage does not support editing sent messages via API. editMessage throws.
  • No recipient-side unsenddeleteMessage does not remove messages already delivered to recipients.
  • No reaction removal — tapbacks can be added but not removed via the API.
  • Typing indicators — 1:1 chats only, not group conversations.

Feature support

Messaging

FeatureSupported
Post message
Edit message
Delete message
File uploadsInbound + via SDK
Streaming
Scheduled messages

Rich content

FeatureSupported
Card format
Buttons
Link buttons
Select menus
Tables
Fields
Images in cards
Modals

Conversations

FeatureSupported
Slash commands
Mentions
Add reactionsTapbacks
Remove reactions
Typing indicator1:1 only
DMs
Ephemeral messages
User lookupevaluateService
Parent subject
Native client
Custom API endpoint

Message history

FeatureSupported
Fetch messages
Fetch single message
Fetch thread info
Fetch channel messages
List threads
Fetch channel info
Post channel message