Twilio
"Twilio: SMS, voice calls, WhatsApp, verify API, programmable messaging, webhooks, Node SDK"
Twilio provides cloud communications APIs that let developers embed messaging, voice, and verification into applications. The platform treats every communication channel — SMS, voice, WhatsApp, email — as a programmable API endpoint. Build around Twilio's event-driven webhook model: your app responds to inbound messages and call events via HTTP endpoints, while outbound communication is initiated through REST API calls. Always use the official Node SDK rather than raw HTTP requests; it handles authentication, retries, and response parsing. Design your messaging flows to be stateless at the webhook level, storing conversation state in your own database. ## Key Points - Always validate webhook signatures in production to prevent spoofed requests - Use Messaging Services instead of single phone numbers for production SMS — they handle sender selection, compliance, and scaling - Store message SIDs and track delivery via status callbacks; never assume a message was delivered - Use the Verify API for OTP/phone verification instead of building your own; it handles rate limiting and fraud detection - Set up fallback URLs on your Twilio phone numbers so failed webhooks have a backup - Handle Twilio error codes gracefully — codes like 21211 (invalid phone) and 21614 (not mobile) need distinct handling - Use TwiML Bins or Twilio Functions for simple static responses rather than hosting your own endpoints - Queue outbound messages during high volume rather than sending all at once; respect per-second rate limits - **Hardcoding credentials** — Never embed account SID or auth token in source code; use environment variables or a secrets manager - **Ignoring status callbacks** — Sending messages without tracking delivery status leads to silent failures and poor user experience - **Raw API calls instead of SDK** — The Node SDK handles auth, retries, and pagination; raw HTTP calls skip these safeguards - **Synchronous webhook processing** — Long-running operations in webhook handlers cause Twilio timeouts (15s for messaging, 5s default for voice); offload heavy work to a queue ## Quick Example ``` TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx TWILIO_AUTH_TOKEN=your_auth_token TWILIO_PHONE_NUMBER=+15551234567 TWILIO_WHATSAPP_NUMBER=whatsapp:+14155238886 TWILIO_VERIFY_SERVICE_SID=VAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ```
skilldb get messaging-services-skills/TwilioFull skill: 282 linesTwilio Messaging & Communications
Core Philosophy
Twilio provides cloud communications APIs that let developers embed messaging, voice, and verification into applications. The platform treats every communication channel — SMS, voice, WhatsApp, email — as a programmable API endpoint. Build around Twilio's event-driven webhook model: your app responds to inbound messages and call events via HTTP endpoints, while outbound communication is initiated through REST API calls. Always use the official Node SDK rather than raw HTTP requests; it handles authentication, retries, and response parsing. Design your messaging flows to be stateless at the webhook level, storing conversation state in your own database.
Setup
Installation and Configuration
import twilio from "twilio";
import express from "express";
// Initialize Twilio client with account credentials
const accountSid = process.env.TWILIO_ACCOUNT_SID!;
const authToken = process.env.TWILIO_AUTH_TOKEN!;
const client = twilio(accountSid, authToken);
// Twilio phone number for sending
const twilioNumber = process.env.TWILIO_PHONE_NUMBER!;
// Express app for receiving webhooks
const app = express();
app.use(express.urlencoded({ extended: true }));
Environment Variables
TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TWILIO_AUTH_TOKEN=your_auth_token
TWILIO_PHONE_NUMBER=+15551234567
TWILIO_WHATSAPP_NUMBER=whatsapp:+14155238886
TWILIO_VERIFY_SERVICE_SID=VAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Key Techniques
Sending SMS Messages
async function sendSms(to: string, body: string): Promise<string> {
const message = await client.messages.create({
body,
from: twilioNumber,
to,
statusCallback: "https://yourapp.com/webhooks/sms-status",
});
console.log(`Message sent: ${message.sid}`);
return message.sid;
}
// Send with media (MMS)
async function sendMms(to: string, body: string, mediaUrl: string): Promise<string> {
const message = await client.messages.create({
body,
from: twilioNumber,
to,
mediaUrl: [mediaUrl],
});
return message.sid;
}
Receiving SMS via Webhooks
import { twiml } from "twilio";
app.post("/webhooks/sms", (req, res) => {
const incomingMessage = req.body.Body;
const fromNumber = req.body.From;
const messageSid = req.body.MessageSid;
console.log(`Received from ${fromNumber}: ${incomingMessage}`);
// Respond with TwiML
const response = new twiml.MessagingResponse();
response.message(`Thanks for your message! We received: "${incomingMessage}"`);
res.type("text/xml");
res.send(response.toString());
});
// Status callback for delivery tracking
app.post("/webhooks/sms-status", (req, res) => {
const { MessageSid, MessageStatus, ErrorCode } = req.body;
console.log(`Message ${MessageSid} status: ${MessageStatus}`);
if (ErrorCode) {
console.error(`Error ${ErrorCode} for message ${MessageSid}`);
}
res.sendStatus(200);
});
WhatsApp Integration
const whatsappNumber = process.env.TWILIO_WHATSAPP_NUMBER!;
async function sendWhatsApp(to: string, body: string): Promise<string> {
const message = await client.messages.create({
body,
from: whatsappNumber,
to: `whatsapp:${to}`,
});
return message.sid;
}
// Send WhatsApp template message (required for initiating conversations)
async function sendWhatsAppTemplate(
to: string,
contentSid: string,
variables: Record<string, string>
): Promise<string> {
const message = await client.messages.create({
from: whatsappNumber,
to: `whatsapp:${to}`,
contentSid,
contentVariables: JSON.stringify(variables),
});
return message.sid;
}
Phone Verification with Verify API
const verifyServiceSid = process.env.TWILIO_VERIFY_SERVICE_SID!;
async function sendVerificationCode(phoneNumber: string, channel: "sms" | "call" = "sms") {
const verification = await client.verify.v2
.services(verifyServiceSid)
.verifications.create({
to: phoneNumber,
channel,
});
return { status: verification.status, sid: verification.sid };
}
async function checkVerificationCode(
phoneNumber: string,
code: string
): Promise<{ valid: boolean; status: string }> {
const check = await client.verify.v2
.services(verifyServiceSid)
.verificationChecks.create({
to: phoneNumber,
code,
});
return { valid: check.status === "approved", status: check.status };
}
Programmable Voice
app.post("/webhooks/voice/inbound", (req, res) => {
const response = new twiml.VoiceResponse();
const gather = response.gather({
numDigits: 1,
action: "/webhooks/voice/menu-selection",
method: "POST",
});
gather.say("Press 1 for sales, press 2 for support.");
// If no input, redirect
response.redirect("/webhooks/voice/inbound");
res.type("text/xml");
res.send(response.toString());
});
app.post("/webhooks/voice/menu-selection", (req, res) => {
const digit = req.body.Digits;
const response = new twiml.VoiceResponse();
if (digit === "1") {
response.dial("+15559876543"); // Sales line
} else if (digit === "2") {
response.enqueue("support"); // Queue for support
} else {
response.say("Invalid selection.");
response.redirect("/webhooks/voice/inbound");
}
res.type("text/xml");
res.send(response.toString());
});
// Initiate an outbound call
async function makeCall(to: string, twimlUrl: string): Promise<string> {
const call = await client.calls.create({
to,
from: twilioNumber,
url: twimlUrl,
});
return call.sid;
}
Webhook Signature Validation
import { validateRequest } from "twilio";
function twilioWebhookMiddleware(
req: express.Request,
res: express.Response,
next: express.NextFunction
) {
const signature = req.headers["x-twilio-signature"] as string;
const url = `${req.protocol}://${req.get("host")}${req.originalUrl}`;
const isValid = validateRequest(authToken, signature, url, req.body);
if (!isValid) {
res.status(403).send("Invalid signature");
return;
}
next();
}
app.use("/webhooks", twilioWebhookMiddleware);
Bulk Messaging with Messaging Services
async function sendBulkSms(
recipients: string[],
body: string,
messagingServiceSid: string
) {
const results = await Promise.allSettled(
recipients.map((to) =>
client.messages.create({
body,
messagingServiceSid, // Uses Twilio Messaging Service for sender pool
to,
})
)
);
const sent = results.filter((r) => r.status === "fulfilled").length;
const failed = results.filter((r) => r.status === "rejected").length;
return { sent, failed, total: recipients.length };
}
Best Practices
- Always validate webhook signatures in production to prevent spoofed requests
- Use Messaging Services instead of single phone numbers for production SMS — they handle sender selection, compliance, and scaling
- Store message SIDs and track delivery via status callbacks; never assume a message was delivered
- Use the Verify API for OTP/phone verification instead of building your own; it handles rate limiting and fraud detection
- Set up fallback URLs on your Twilio phone numbers so failed webhooks have a backup
- Handle Twilio error codes gracefully — codes like 21211 (invalid phone) and 21614 (not mobile) need distinct handling
- Use TwiML Bins or Twilio Functions for simple static responses rather than hosting your own endpoints
- Queue outbound messages during high volume rather than sending all at once; respect per-second rate limits
Anti-Patterns
- Hardcoding credentials — Never embed account SID or auth token in source code; use environment variables or a secrets manager
- Ignoring status callbacks — Sending messages without tracking delivery status leads to silent failures and poor user experience
- Raw API calls instead of SDK — The Node SDK handles auth, retries, and pagination; raw HTTP calls skip these safeguards
- Synchronous webhook processing — Long-running operations in webhook handlers cause Twilio timeouts (15s for messaging, 5s default for voice); offload heavy work to a queue
- Skipping phone number validation — Sending to invalid numbers wastes money and hurts sender reputation; validate with Lookup API first
- Using trial numbers in production — Trial accounts prepend "Sent from your Twilio trial account" to every message and have limited throughput
- Not handling opt-outs — For SMS, you must respect STOP/UNSUBSCRIBE keywords; Twilio handles this automatically on long codes but you must track it in your database
Install this skill directly: skilldb add messaging-services-skills
Related Skills
Ably
"Ably: real-time messaging, pub/sub, presence, message history, push notifications, WebSocket/SSE"
Knock
"Knock: notification infrastructure, in-app feeds, workflows, preferences, channels, React components"
Novu
"Novu: notification infrastructure, multi-channel (email/SMS/push/in-app), templates, preferences, digest, workflows"
OneSignal
"OneSignal: push notifications, in-app messaging, email, SMS, segments, journeys, REST API"
Pusher
"Pusher: real-time WebSocket channels, presence channels, private channels, triggers, React hooks"
Sendbird
"Sendbird: chat SDK, group channels, open channels, messages, file sharing, moderation, UIKit components"