Sendblue

Community Sendblue adapter for Chat SDK. Send and receive iMessage (with SMS fallback) through Sendblue's hosted gateway, with tapbacks, typing indicators, and number lookup.

Community adapter. Not maintained by Vercel or Chat SDK contributors. For feature requests, bug reports, and support, file an issue on the adapter's repo.

Install

pnpm add chat-adapter-sendblue

Quick start

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

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

createSendblueAdapter() reads credentials from environment variables by default:

VariableRequiredDescription
SENDBLUE_API_KEYYesSendblue API key ID
SENDBLUE_API_SECRETYesSendblue API secret key
SENDBLUE_FROM_NUMBERYesYour registered Sendblue phone number (E.164)
SENDBLUE_WEBHOOK_SECRETNoSecret for webhook signature verification
SENDBLUE_STATUS_CALLBACK_URLNoURL for outbound message status updates

Or pass them explicitly:

createSendblueAdapter({
  apiKey: "sb-api-key-...",
  apiSecret: "sb-api-secret-...",
  defaultFromNumber: "+14155551234",
});

Webhooks

Point your Sendblue webhook URLs at your server. The adapter handles three webhook types:

  • Inbound messages — incoming iMessage/SMS routed to your bot
  • Outbound status — delivery confirmations for messages you sent
  • Typing indicators — when a contact starts typing
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);
}

Webhook verification

If you configure a webhook secret in Sendblue, pass it as SENDBLUE_WEBHOOK_SECRET (or in the config). The adapter checks the x-webhook-secret header on every request. Override the header name with webhookSecretHeader:

createSendblueAdapter({
  webhookSecret: "my-secret",
  webhookSecretHeader: "x-custom-header",
});

Sending messages

The adapter sends outbound iMessages (with SMS fallback) through Chat SDK's standard postMessage interface. Markdown is automatically stripped to plain text since iMessage does not render it.

await chat.send("sendblue", threadId, "Hello from the bot!");

Attachments

Inbound media URLs from Sendblue are parsed into Chat SDK attachment objects with auto-detected MIME types. To send media, drop down to Sendblue's media_url parameter through the SDK directly:

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: "+14155551234",
  content: "Check this out",
  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 "..." bubble to the recipient. Only supported for 1:1 conversations (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 or SMS:

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

Read receipts

Send read receipts for a conversation (requires Sendblue account-level activation):

const adapter = chat.getAdapter("sendblue") as SendblueAdapter;
await adapter.markRead(threadId);

Direct SDK access

For anything not covered by the Chat SDK adapter interface, drop down to the official 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 only processes inbound messages delivered via iMessage. 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 are sticky 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.

Limitations

  • No message editing — iMessage does not support editing sent messages via API. editMessage throws.
  • No unsenddeleteMessage is a soft-delete in Sendblue's database only.
  • No reaction removal — tapbacks can be added but not removed via the API.
  • Inbound media expiry — Sendblue inbound media_url values expire after 30 days. Persist them if you need them long-term.
  • 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