Skip to navigation

Vercel

Chat SDK by Vercel is the open-source toolkit for building chat interfaces — it powers conversational UIs across Slack, Teams, Discord, WhatsApp, Telegram, and more. AgentPhone is an official vendor adapter, bringing SMS, MMS, iMessage, and voice to the Chat SDK ecosystem through one interface.

Four channels, one adapter

ChannelSendReceiveMediaReactions
SMSYesYes——
MMSYesYesYes—
iMessageYesYesYesYes
Voice—Transcripts——

Inbound SMS, MMS, and iMessage arrive as standard Chat SDK message events. Voice calls land as messages containing the full transcript and summary when the call ends — your bot logic handles everything through the same handler pipeline.

Get started

npm install @agentphone/chat-sdk-adapter chat @chat-adapter/state-memory
import { Chat } from "chat";
import { createMemoryState } from "@chat-adapter/state-memory";
import { createAgentPhoneAdapter } from "@agentphone/chat-sdk-adapter";
for (const name of ["AGENTPHONE_API_KEY", "AGENTPHONE_AGENT_ID", "AGENTPHONE_WEBHOOK_SECRET"]) {
if (!process.env[name]) throw new Error(`Set ${name}`);
}
const agentphone = createAgentPhoneAdapter({
apiKey: process.env.AGENTPHONE_API_KEY!,
agentId: process.env.AGENTPHONE_AGENT_ID!,
webhookSecret: process.env.AGENTPHONE_WEBHOOK_SECRET!,
});
export const chat = new Chat({
userName: "my-bot",
adapters: { agentphone },
state: createMemoryState(),
});
chat.onNewMention(async (thread, message) => {
await thread.subscribe();
await thread.post(`Got your message: ${message.text}`);
});

Save the setup as lib/chat.ts. Set AGENTPHONE_API_KEY, AGENTPHONE_AGENT_ID, and AGENTPHONE_WEBHOOK_SECRET on the server. Signature verification only runs when a webhook secret is configured; fail deployment if any required variable is missing.

For a Next.js App Router endpoint, save this as app/api/webhook/route.ts:

import { chat } from "../../../lib/chat";
export async function POST(request: Request) {
return chat.webhooks.agentphone(request);
}

Point your AgentPhone webhook at that route. Save the secret returned by registration. This example uses in-memory Chat SDK state for local development; use a supported durable state adapter for multi-instance or serverless deployments. Handle subscribed messages as well as new mentions:

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

iMessage reactions

The AgentPhone adapter supports iMessage tapbacks — send and receive love, like, dislike, laugh, emphasize, and question:

await chat.getAdapter("agentphone").addReaction(threadId, messageId, "love");

Inbound reactions fire the standard onReaction handler — no special handling required.

Voice call transcripts

When a call ends, the transcript arrives as a Chat SDK message with the full text, a summary, and call metadata:

chat.onNewMention(async (thread, message) => {
const raw = message.raw;
if (raw && typeof raw === "object" && "callId" in raw) {
console.log("Completed call:", raw.callId);
if ("summary" in raw && typeof raw.summary === "string") console.log(raw.summary);
if ("durationSeconds" in raw && typeof raw.durationSeconds === "number") {
console.log("Duration (seconds):", raw.durationSeconds);
}
if ("transcript" in raw && Array.isArray(raw.transcript)) {
for (const turn of raw.transcript) {
if (turn && typeof turn === "object" && typeof turn.content === "string") {
console.log(turn.content);
}
}
}
}
});

This adapter processes completed-call notifications; it does not supply live voice turn responses. Use hosted voice or a voice webhook for the conversation itself. A follow-up SMS is a separate send and needs outbound SMS readiness.