Send and receive SMS messages in your Convex app using Twilio.
Create a Twilio account and, if you haven't already, create a Twilio Phone Number.
Note the Phone Number SID of the phone number you'll be using, you'll need it in a moment.
You'll need a Convex App to use the component. Follow any of the Convex quickstarts to set one up.
Install the component package:
npm install @convex-dev/twilioCreate a convex.config.ts file in your app's convex/ folder and install the component by calling use:
// convex/convex.config.js import { defineApp } from "convex/server"; import twilio from "@convex-dev/twilio/convex.config.js"; const app = defineApp(); app.use(twilio); export default app;Set your API credentials:
npx convex env set TWILIO_ACCOUNT_SID=ACxxxxx npx convex env set TWILIO_AUTH_TOKEN=xxxxxInstantiate a Twilio Component client in a file in your app's convex/ folder:
// convex/example.ts import Twilio from "@convex-dev/twilio"; import { components } from "./_generated/server.js"; const twilio = twilioClient(components.twilio, { // optionally pass in the default "from" phone number you'll be using // this must be a phone number you've created with Twilio default_from: process.env.TWILIO_PHONE_NUMBER!, }); // export to be used everywhere in your /convex code export default twilio;Register Twilio webhook handlers by creating an http.ts file in your convex/ folder and use the client you've exported above:
Or, if you already have an http router:
// http.ts import twilio from "./example"; import { httpRouter } from "convex/server"; const http = httpRouter(); twilio.registerRoutes(http); export default http;This will register two webhook HTTP handlers in your your Convex app's deployment:
- YOUR_CONVEX_SITE_URL/twilio/message-status - capture and store delivery status of messages you send.
- YOUR_CONVEX_SITE_URL/twilio/incoming-message - capture and store messages sent to your Twilio phone number.
Note: if you want to route twilio endpoints somewhere else, pass a custom http_prefix
To send a message use the Convex action sendMessage exposed by the client, for example:
// convex/messages.ts import { v } from "convex/values"; import { internalAction } from "./_generated/server"; import twilio from "./twilio"; export const sendSms = internalAction({ args: { to: v.string(), body: v.string(), }, handler: async (ctx, args) => { return await twilio.sendMessage(ctx, args); }, });By querying the message (see below) you can check for the status (Twilio Statuses). The component subscribes to status updates and writes the most up-to-date status into the database.
To receive messages, you will associate a webhook handler provided by the component with the Twilio phone number you'd like to use. The webhook handler is mounted at
YOUR_CONVEX_SITE_URL/incoming-message You can associate it with your Twilio phone number in two ways:
-
Using the Twilio console in the "Configure" tab of the phone number, under "Messaging Configuration" -> "A messsage comes in" -> "URL".
-
By calling
registerIncomingSmsHandlerexposed by the component client, passing it the phone number's SID:
// convex/messages.ts // ... export const registerIncomingSmsHandler = internalAction({ args: {}, handler: async (ctx) => { return await twilio.registerIncomingSmsHandler(ctx, { sid: "YOUR_TWILIO_PHONE_NUMBER_SID", }); }, });Now, incoming messages will be captured by the component and logged in the messages table.
You can execute your own logic upon receiving an incoming message, by providing a callback when instantiating the Twilio Component client:
// convex/example.ts const twilio = twilioClient(components.twilio, { default_from: process.env.TWILIO_PHONE_NUMBER || "", incomingMessageCallback: async (ctx, message) => { // use ctx here to execute other Convex functions console.log("Incoming message", message); } });To list all the mssages, use the list method in your Convex function.
To list all the incoming or outgoing messages, use listIncoming and listOutgoing methods:
// convex/messages.ts // ... export const list = query({ args: {}, handler: async (ctx) => { return await twilio.list(ctx); }, }); export const listIncoming = query({ args: {}, handler: async (ctx) => { return await twilio.listIncoming(ctx); }, }); export const listOutgoing = query({ args: {}, handler: async (ctx) => { return await twilio.listOutgoing(ctx); }, });To get a single message by its sid, use getMessageBySid:
export const getMessageBySid = query({ args: { sid: v.string(), }, handler: async (ctx, args) => { return await twilio.getMessageBySid(ctx, args); } })Get messages by the "to" phone number:
export const getMessagesTo = query({ args: { to: v.string(), }, handler: async (ctx, args) => { return await twilio.getMessagesTo(ctx, args); } })Get messages by the "from" phone number:
export const getMessagesFrom = query({ args: { from: v.string(), }, handler: async (ctx, args) => { return await twilio.getMessagesFrom(ctx, args); } })You can also get all messages to and from a particular number:
export const getMessagesByCounterparty = query({ args: { from: v.string(), }, handler: async (ctx, args) => { return await twilio.getMessagesByCounterparty(ctx, args); } })