twilio-sms

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

twilio-sms

Twilio SMS

Purpose

用途

Enable OpenClaw to implement and operate Twilio Programmable Messaging (SMS/MMS) in production:
  • Send SMS/MMS reliably (Messaging Services, geo-matching, sticky sender, media constraints).
  • Receive inbound messages via webhooks and respond with TwiML.
  • Track delivery lifecycle via status callbacks (queued/sent/delivered/undelivered/failed).
  • Implement opt-out/STOP compliance and keyword workflows.
  • Operate A2P 10DLC (US long code) and toll-free verification constraints.
  • Debug and harden: signature validation, retries, idempotency, rate limits, carrier errors.
This skill is for engineers building messaging pipelines, customer notifications, 2-way support, and compliance-sensitive messaging.

帮助OpenClaw在生产环境中实现并运行Twilio可编程消息服务(SMS/MMS):
  • 可靠发送SMS/MMS(消息服务、地理匹配、固定发送方、媒体限制)。
  • 通过Webhook接收入站消息并使用TwiML回复。
  • 通过状态回调跟踪送达生命周期(排队/发送/已送达/未送达/失败)。
  • 实现退订/STOP合规性和关键词工作流。
  • 遵循A2P 10DLC(美国长码)和免费电话验证限制。
  • 调试与加固:签名验证、重试、幂等性、速率限制、运营商错误处理。
本技能适用于构建消息管道、客户通知、双向支持和合规敏感型消息的工程师。

Prerequisites

前置条件

Accounts & Twilio Console setup

账户与Twilio控制台设置

  • Twilio account with Programmable Messaging enabled.
  • At least one of:
    • Messaging Service (recommended) with sender pool (long codes / toll-free / short code).
    • A dedicated Phone Number capable of SMS/MMS.
  • For US A2P 10DLC:
    • Brand + Campaign registration completed in Twilio Console (Messaging → Regulatory Compliance / A2P 10DLC).
  • For toll-free:
    • Toll-free verification submitted/approved if sending high volume to US/CA.
  • 已启用可编程消息服务的Twilio账户。
  • 至少具备以下其中一项:
    • 消息服务(推荐),包含发送方池(长码/免费电话/短码)。
    • 支持SMS/MMS的专用电话号码
  • 针对美国A2P 10DLC:
    • 已在Twilio控制台完成品牌+活动注册(消息→合规监管/A2P 10DLC)。
  • 针对免费电话:
    • 若向美国/加拿大发送高流量消息,需提交并通过免费电话验证。

Runtime versions (tested)

已测试的运行时版本

  • Node.js 20.11.1 (LTS) + npm 10.2.4
  • Python 3.11.7
  • Twilio SDKs:
    • twilio
      (Node) 4.23.0
    • twilio
      (Python) 9.4.1
  • Web framework examples:
    • Express 4.18.3
    • FastAPI 0.109.2 + Uvicorn 0.27.1
  • Optional tooling:
    • Twilio CLI 5.16.0
    • ngrok 3.13.1 (local webhook tunneling)
    • Docker 25.0.3 + Compose v2 2.24.6
  • Node.js 20.11.1(LTS)+ npm 10.2.4
  • Python 3.11.7
  • Twilio SDK:
    • Node版
      twilio
      4.23.0
    • Python版
      twilio
      9.4.1
  • Web框架示例:
    • Express 4.18.3
    • FastAPI 0.109.2 + Uvicorn 0.27.1
  • 可选工具:
    • Twilio CLI 5.16.0
    • ngrok 3.13.1(本地Webhook隧道)
    • Docker 25.0.3 + Compose v2 2.24.6

Credentials & auth

凭证与认证

You need:
  • TWILIO_ACCOUNT_SID
    (starts with
    AC...
    )
  • TWILIO_AUTH_TOKEN
  • One of:
    • TWILIO_MESSAGING_SERVICE_SID
      (starts with
      MG...
      ) preferred
    • TWILIO_FROM_NUMBER
      (E.164, e.g.
      +14155552671
      )
Store secrets in a secret manager (AWS Secrets Manager / GCP Secret Manager / Vault). For local dev,
.env
is acceptable.
你需要:
  • TWILIO_ACCOUNT_SID
    (以
    AC...
    开头)
  • TWILIO_AUTH_TOKEN
  • 以下其中一项:
    • TWILIO_MESSAGING_SERVICE_SID
      (以
      MG...
      开头)优先推荐
    • TWILIO_FROM_NUMBER
      (E.164格式,例如
      +14155552671
将密钥存储在密钥管理器中(AWS Secrets Manager / GCP Secret Manager / Vault)。本地开发时,可使用
.env
文件。

Network & webhook requirements

网络与Webhook要求

  • Public HTTPS endpoint for inbound and status callbacks.
  • Must accept
    application/x-www-form-urlencoded
    (Twilio default) and/or JSON depending on endpoint.
  • Validate Twilio signatures (
    X-Twilio-Signature
    ) on inbound webhooks.

  • 用于入站和状态回调的公共HTTPS端点。
  • 必须支持
    application/x-www-form-urlencoded
    (Twilio默认格式),或根据端点需求支持JSON。
  • 验证入站Webhook的Twilio签名(
    X-Twilio-Signature
    )。

Core Concepts

核心概念

Programmable Messaging objects

可编程消息对象

  • Message: a single outbound or inbound SMS/MMS. Identified by
    MessageSid
    (
    SM...
    ).
  • Messaging Service: abstraction over senders; supports:
    • sender pool
    • geo-matching
    • sticky sender
    • smart encoding
    • status callback configuration
  • Status Callback: webhook invoked as message state changes.
  • Inbound Webhook: webhook invoked when Twilio receives an inbound message to your number/service.
  • Message:单条出站或入站SMS/MMS,由
    MessageSid
    SM...
    )标识。
  • Messaging Service:发送方的抽象层,支持:
    • 发送方池
    • 地理匹配
    • 固定发送方
    • 智能编码
    • 状态回调配置
  • Status Callback:消息状态变更时触发的Webhook。
  • Inbound Webhook:Twilio收到发送至你的号码/服务的入站消息时触发的Webhook。

Delivery lifecycle (practical)

送达生命周期(实际场景)

Typical
MessageStatus
values you will see:
  • queued
    sending
    sent
    delivered
  • Failure paths:
    • undelivered
      (carrier rejected / unreachable)
    • failed
      (Twilio could not send; configuration/auth issues)
Treat
sent
as “handed to carrier”, not “delivered”.
你会遇到的典型
MessageStatus
值:
  • queued
    sending
    sent
    delivered
  • 失败路径:
    • undelivered
      (运营商拒绝/无法到达)
    • failed
      (Twilio无法发送;配置/认证问题)
注意:
sent
仅表示“已提交给运营商”,而非“已送达”。

TwiML for Messaging

消息用TwiML

Inbound SMS/MMS webhooks can respond with TwiML:
xml
<Response>
  <Message>Thanks. We received your message.</Message>
</Response>
Use TwiML for synchronous replies; use REST API for async workflows.
入站SMS/MMS Webhook可通过TwiML回复:
xml
<Response>
  <Message>Thanks. We received your message.</Message>
</Response>
同步回复使用TwiML;异步工作流使用REST API。

Opt-out compliance

退订合规性

  • Twilio automatically handles standard opt-out keywords (e.g.,
    STOP
    ,
    UNSUBSCRIBE
    ).
  • When a user opts out, Twilio blocks further messages from that sender/service to that recipient until they opt back in (
    START
    ).
  • Your app should:
    • treat opt-out as a first-class state
    • avoid retry storms on blocked recipients
    • log and suppress sends to opted-out numbers
  • Twilio自动处理标准退订关键词(如
    STOP
    UNSUBSCRIBE
    )。
  • 用户退订后,Twilio会阻止该发送方/服务向该收件人发送进一步消息,直到用户重新订阅(
    START
    )。
  • 你的应用应:
    • 将退订作为核心状态处理
    • 避免向被阻止的收件人重复发送
    • 记录并抑制向已退订号码发送消息

A2P 10DLC / short codes / toll-free

A2P 10DLC / 短码 / 免费电话

  • US A2P 10DLC: required for application-to-person messaging over US long codes at scale. Unregistered traffic may be filtered or blocked.
  • Short codes: high throughput, expensive, long provisioning.
  • Toll-free: good for US/CA; verification improves deliverability and throughput.
  • 美国A2P 10DLC:通过美国长码大规模发送应用到用户消息的必填项。未注册流量可能被过滤或阻止。
  • 短码:高吞吐量、成本高、配置周期长。
  • 免费电话:适用于美国/加拿大;验证可提升送达率和吞吐量。

Webhook retries and idempotency

Webhook重试与幂等性

Twilio retries webhooks on non-2xx responses. Your webhook handlers must be:
  • idempotent (dedupe by
    MessageSid
    /
    SmsSid
    )
  • fast (respond quickly; enqueue work)
  • resilient (return 2xx once accepted)

Twilio会对非2xx响应的Webhook进行重试。你的Webhook处理程序必须:
  • 幂等(通过
    MessageSid
    /
    SmsSid
    去重)
  • 快速(快速响应;将工作加入队列)
  • resilient(接收后立即返回2xx)

Installation & Setup

安装与设置

Official Python SDK — Messaging

官方Python SDK — 消息服务

Repository: https://github.com/twilio/twilio-python
PyPI:
pip install twilio
· Supported: Python 3.7–3.13
python
from twilio.rest import Client
client = Client()  # TWILIO_ACCOUNT_SID + TWILIO_AUTH_TOKEN from env
仓库地址: https://github.com/twilio/twilio-python
PyPI安装:
pip install twilio
· 支持版本: Python 3.7–3.13
python
from twilio.rest import Client
client = Client()  # 从环境变量读取TWILIO_ACCOUNT_SID + TWILIO_AUTH_TOKEN

Send SMS

发送SMS

msg = client.messages.create( body="Hello from Python!", from_="+15017250604", to="+15558675309" ) print(msg.sid)
msg = client.messages.create( body="Hello from Python!", from_="+15017250604", to="+15558675309" ) print(msg.sid)

List recent messages

列出最近的消息

for m in client.messages.list(limit=20): print(m.body, m.status)

Source: [twilio/twilio-python — messages](https://github.com/twilio/twilio-python/blob/main/twilio/rest/api/v2010/account/message/__init__.py)
for m in client.messages.list(limit=20): print(m.body, m.status)

来源:[twilio/twilio-python — messages](https://github.com/twilio/twilio-python/blob/main/twilio/rest/api/v2010/account/message/__init__.py)

Ubuntu 22.04 (x86_64 / ARM64)

Ubuntu 22.04(x86_64 / ARM64)

bash
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg jq
Node.js 20 via NodeSource:
bash
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
node -v   # v20.11.1 (or later 20.x)
npm -v    # 10.2.4 (or later)
Python 3.11:
bash
sudo apt-get install -y python3.11 python3.11-venv python3-pip
python3.11 --version
Twilio CLI 5.16.0:
bash
npm install -g twilio-cli@5.16.0
twilio --version
ngrok 3.13.1 (optional):
bash
curl -fsSL https://ngrok-agent.s3.amazonaws.com/ngrok.asc \
  | sudo gpg --dearmor -o /usr/share/keyrings/ngrok.gpg
echo "deb [signed-by=/usr/share/keyrings/ngrok.gpg] https://ngrok-agent.s3.amazonaws.com buster main" \
  | sudo tee /etc/apt/sources.list.d/ngrok.list
sudo apt-get update && sudo apt-get install -y ngrok
ngrok version
bash
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg jq
通过NodeSource安装Node.js 20:
bash
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
node -v   # v20.11.1(或更高20.x版本)
npm -v    # 10.2.4(或更高版本)
安装Python 3.11:
bash
sudo apt-get install -y python3.11 python3.11-venv python3-pip
python3.11 --version
安装Twilio CLI 5.16.0:
bash
npm install -g twilio-cli@5.16.0
twilio --version
安装ngrok 3.13.1(可选):
bash
curl -fsSL https://ngrok-agent.s3.amazonaws.com/ngrok.asc \
  | sudo gpg --dearmor -o /usr/share/keyrings/ngrok.gpg
echo "deb [signed-by=/usr/share/keyrings/ngrok.gpg] https://ngrok-agent.s3.amazonaws.com buster main" \
  | sudo tee /etc/apt/sources.list.d/ngrok.list
sudo apt-get update && sudo apt-get install -y ngrok
ngrok version

Fedora 39 (x86_64 / ARM64)

Fedora 39(x86_64 / ARM64)

bash
sudo dnf install -y curl jq nodejs python3.11 python3.11-pip
node -v
python3.11 --version
Twilio CLI:
bash
sudo npm install -g twilio-cli@5.16.0
twilio --version
bash
sudo dnf install -y curl jq nodejs python3.11 python3.11-pip
node -v
python3.11 --version
安装Twilio CLI:
bash
sudo npm install -g twilio-cli@5.16.0
twilio --version

macOS (Intel + Apple Silicon)

macOS(Intel + Apple Silicon)

Homebrew:
bash
brew update
brew install node@20 python@3.11 jq
Ensure PATH:
bash
echo 'export PATH="/opt/homebrew/opt/node@20/bin:/opt/homebrew/opt/python@3.11/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
node -v
python3.11 --version
Twilio CLI:
bash
npm install -g twilio-cli@5.16.0
twilio --version
ngrok:
bash
brew install ngrok/ngrok/ngrok
ngrok version
通过Homebrew安装:
bash
brew update
brew install node@20 python@3.11 jq
配置PATH:
bash
echo 'export PATH="/opt/homebrew/opt/node@20/bin:/opt/homebrew/opt/python@3.11/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
node -v
python3.11 --version
安装Twilio CLI:
bash
npm install -g twilio-cli@5.16.0
twilio --version
安装ngrok:
bash
brew install ngrok/ngrok/ngrok
ngrok version

Docker (all platforms)

Docker(全平台)

bash
docker --version
docker compose version
bash
docker --version
docker compose version

Twilio CLI authentication

Twilio CLI认证

Interactive login:
bash
twilio login
Or set env vars (CI):
bash
export TWILIO_ACCOUNT_SID="YOUR_ACCOUNT_SID"
export TWILIO_AUTH_TOKEN="your_auth_token"
Verify:
bash
twilio api:core:accounts:fetch --sid "$TWILIO_ACCOUNT_SID"
交互式登录:
bash
twilio login
或设置环境变量(CI场景):
bash
export TWILIO_ACCOUNT_SID="YOUR_ACCOUNT_SID"
export TWILIO_AUTH_TOKEN="your_auth_token"
验证:
bash
twilio api:core:accounts:fetch --sid "$TWILIO_ACCOUNT_SID"

Local webhook tunneling (ngrok)

本地Webhook隧道(ngrok)

bash
ngrok http 3000
bash
ngrok http 3000

note the https forwarding URL, e.g. https://f3a1-203-0-113-10.ngrok-free.app

记录HTTPS转发URL,例如https://f3a1-203-0-113-10.ngrok-free.app


Configure Twilio inbound webhook to:

- `https://.../twilio/inbound`
- Status callback to:
  - `https://.../twilio/status`

---

配置Twilio入站Webhook为:

- `https://.../twilio/inbound`
- 状态回调为:
  - `https://.../twilio/status`

---

Key Capabilities

核心功能

Send SMS/MMS (REST API)

发送SMS/MMS(REST API)

  • Use Messaging Service (
    messagingServiceSid
    ) for production.
  • Use
    statusCallback
    for delivery receipts.
  • Use
    provideFeedback=true
    for carrier feedback (where supported).
Node (SMS):
javascript
import twilio from "twilio";

const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);

const msg = await client.messages.create({
  messagingServiceSid: process.env.TWILIO_MESSAGING_SERVICE_SID,
  to: "+14155550123",
  body: "Build 742 deployed. Reply STOP to opt out.",
  statusCallback: "https://api.example.com/twilio/status",
  provideFeedback: true,
});

console.log(msg.sid, msg.status);
Python (MMS):
python
from twilio.rest import Client
import os

client = Client(os.environ["TWILIO_ACCOUNT_SID"], os.environ["TWILIO_AUTH_TOKEN"])

msg = client.messages.create(
    messaging_service_sid=os.environ["TWILIO_MESSAGING_SERVICE_SID"],
    to="+14155550123",
    body="Here is the incident screenshot.",
    media_url=["https://cdn.example.com/incidents/INC-2048.png"],
    status_callback="https://api.example.com/twilio/status",
)
print(msg.sid, msg.status)
Production constraints for MMS:
  • Media must be publicly reachable via HTTPS.
  • Content-type and size limits vary by carrier; keep images small (< 500KB) when possible.
  • Use signed URLs with sufficient TTL (>= 1 hour) if private.

  • 生产环境使用消息服务(
    messagingServiceSid
    )。
  • 使用
    statusCallback
    获取送达回执。
  • (支持的场景)使用
    provideFeedback=true
    获取运营商反馈。
Node.js(SMS示例):
javascript
import twilio from "twilio";

const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);

const msg = await client.messages.create({
  messagingServiceSid: process.env.TWILIO_MESSAGING_SERVICE_SID,
  to: "+14155550123",
  body: "Build 742 deployed. Reply STOP to opt out.",
  statusCallback: "https://api.example.com/twilio/status",
  provideFeedback: true,
});

console.log(msg.sid, msg.status);
Python(MMS示例):
python
from twilio.rest import Client
import os

client = Client(os.environ["TWILIO_ACCOUNT_SID"], os.environ["TWILIO_AUTH_TOKEN"])

msg = client.messages.create(
    messaging_service_sid=os.environ["TWILIO_MESSAGING_SERVICE_SID"],
    to="+14155550123",
    body="Here is the incident screenshot.",
    media_url=["https://cdn.example.com/incidents/INC-2048.png"],
    status_callback="https://api.example.com/twilio/status",
)
print(msg.sid, msg.status)
MMS生产环境限制:
  • 媒体文件必须可通过HTTPS公开访问。
  • 内容类型和大小限制因运营商而异;尽可能将图片控制在500KB以内。
  • 若为私有文件,使用有效期足够长(≥1小时)的签名URL。

Receive inbound SMS/MMS (webhook)

接收入站SMS/MMS(Webhook)

Inbound webhook receives form-encoded fields like:
  • From
    ,
    To
    ,
    Body
  • MessageSid
    (or
    SmsSid
    legacy)
  • NumMedia
    ,
    MediaUrl0
    ,
    MediaContentType0
    , ...
Express handler with signature validation:
javascript
import express from "express";
import twilio from "twilio";

const app = express();

// Twilio sends application/x-www-form-urlencoded by default
app.use(express.urlencoded({ extended: false }));

app.post("/twilio/inbound", (req, res) => {
  const signature = req.header("X-Twilio-Signature") || "";
  const url = "https://api.example.com/twilio/inbound"; // must match public URL exactly

  const isValid = twilio.validateRequest(
    process.env.TWILIO_AUTH_TOKEN,
    signature,
    url,
    req.body
  );

  if (!isValid) {
    return res.status(403).send("Invalid signature");
  }

  const from = req.body.From;
  const body = (req.body.Body || "").trim();

  // Fast path: respond immediately; enqueue work elsewhere
  const twiml = new twilio.twiml.MessagingResponse();
  if (body.toUpperCase() === "HELP") {
    twiml.message("Support: https://status.example.com. Reply STOP to opt out.");
  } else {
    twiml.message("Received. Ticket created.");
  }

  res.type("text/xml").send(twiml.toString());
});

app.listen(3000);
FastAPI handler:
python
from fastapi import FastAPI, Request, Response
from twilio.request_validator import RequestValidator
from twilio.twiml.messaging_response import MessagingResponse
import os

app = FastAPI()
validator = RequestValidator(os.environ["TWILIO_AUTH_TOKEN"])

@app.post("/twilio/inbound")
async def inbound(request: Request):
    form = await request.form()
    signature = request.headers.get("X-Twilio-Signature", "")
    url = "https://api.example.com/twilio/inbound"

    if not validator.validate(url, dict(form), signature):
        return Response("Invalid signature", status_code=403)

    body = (form.get("Body") or "").strip()
    resp = MessagingResponse()
    resp.message("Received.")
    return Response(str(resp), media_type="text/xml")

入站Webhook会接收表单编码字段,例如:
  • From
    ,
    To
    ,
    Body
  • MessageSid
    (或旧版
    SmsSid
  • NumMedia
    ,
    MediaUrl0
    ,
    MediaContentType0
    , ...
带签名验证的Express处理程序:
javascript
import express from "express";
import twilio from "twilio";

const app = express();

// Twilio默认发送application/x-www-form-urlencoded格式
app.use(express.urlencoded({ extended: false }));

app.post("/twilio/inbound", (req, res) => {
  const signature = req.header("X-Twilio-Signature") || "";
  const url = "https://api.example.com/twilio/inbound"; // 必须与公开URL完全匹配

  const isValid = twilio.validateRequest(
    process.env.TWILIO_AUTH_TOKEN,
    signature,
    url,
    req.body
  );

  if (!isValid) {
    return res.status(403).send("Invalid signature");
  }

  const from = req.body.From;
  const body = (req.body.Body || "").trim();

  // 快速路径:立即回复;将其他工作加入队列
  const twiml = new twilio.twiml.MessagingResponse();
  if (body.toUpperCase() === "HELP") {
    twiml.message("Support: https://status.example.com. Reply STOP to opt out.");
  } else {
    twiml.message("Received. Ticket created.");
  }

  res.type("text/xml").send(twiml.toString());
});

app.listen(3000);
FastAPI处理程序:
python
from fastapi import FastAPI, Request, Response
from twilio.request_validator import RequestValidator
from twilio.twiml.messaging_response import MessagingResponse
import os

app = FastAPI()
validator = RequestValidator(os.environ["TWILIO_AUTH_TOKEN"])

@app.post("/twilio/inbound")
async def inbound(request: Request):
    form = await request.form()
    signature = request.headers.get("X-Twilio-Signature", "")
    url = "https://api.example.com/twilio/inbound"

    if not validator.validate(url, dict(form), signature):
        return Response("Invalid signature", status_code=403)

    body = (form.get("Body") or "").strip()
    resp = MessagingResponse()
    resp.message("Received.")
    return Response(str(resp), media_type="text/xml")

Delivery receipts (status callback webhook)

送达回执(状态回调Webhook)

Status callback receives:
  • MessageSid
  • MessageStatus
    (
    queued
    ,
    sent
    ,
    delivered
    ,
    undelivered
    ,
    failed
    )
  • To
    ,
    From
  • ErrorCode
    (e.g.,
    30003
    )
  • ErrorMessage
    (sometimes present)
Express:
javascript
app.post("/twilio/status", express.urlencoded({ extended: false }), async (req, res) => {
  // Validate signature same as inbound; use exact public URL
  const messageSid = req.body.MessageSid;
  const status = req.body.MessageStatus;
  const errorCode = req.body.ErrorCode ? Number(req.body.ErrorCode) : null;

  // Idempotency: upsert by messageSid + status
  // Example: write to DB with unique constraint (messageSid, status)
  console.log({ messageSid, status, errorCode });

  res.status(204).send();
});
Operational guidance:
  • Treat callbacks as at-least-once delivery.
  • Persist state transitions; do not assume ordering.
  • Use callbacks to:
    • mark notifications delivered
    • trigger fallback channels (email/push) on
      undelivered
      /
      failed
    • compute deliverability metrics by carrier/region

状态回调会接收:
  • MessageSid
  • MessageStatus
    queued
    ,
    sent
    ,
    delivered
    ,
    undelivered
    ,
    failed
  • To
    ,
    From
  • ErrorCode
    (例如
    30003
  • ErrorMessage
    (有时存在)
Express示例:
javascript
app.post("/twilio/status", express.urlencoded({ extended: false }), async (req, res) => {
  // 签名验证方式与入站Webhook相同;使用精确的公开URL
  const messageSid = req.body.MessageSid;
  const status = req.body.MessageStatus;
  const errorCode = req.body.ErrorCode ? Number(req.body.ErrorCode) : null;

  // 幂等性:通过messageSid + status进行更新插入
  // 示例:写入带唯一约束(messageSid, status)的数据库
  console.log({ messageSid, status, errorCode });

  res.status(204).send();
});
操作指南:
  • 回调至少会触发一次。
  • 持久化状态转换;不要假设顺序。
  • 使用回调:
    • 标记通知已送达
    • 收到
      undelivered
      /
      failed
      时触发备用渠道(邮件/推送)
    • 按运营商/地区计算送达率指标

Opt-out / STOP handling

退订/STOP处理

Twilio blocks messages to opted-out recipients automatically. Your system should:
  • Detect opt-out keywords on inbound messages and update your own contact preferences.
  • Suppress sends to opted-out recipients to avoid repeated 21610 errors.
Inbound keyword handling:
javascript
const normalized = body.trim().toUpperCase();
const isStop = ["STOP", "STOPALL", "UNSUBSCRIBE", "CANCEL", "END", "QUIT"].includes(normalized);
const isStart = ["START", "YES", "UNSTOP"].includes(normalized);

if (isStop) {
  // mark user opted out in your DB
}
if (isStart) {
  // mark user opted in
}
When sending, pre-check your DB opt-out state. If you still hit Twilio block, handle error code
21610
.

Twilio会自动阻止向已退订收件人发送消息。你的系统应:
  • 检测入站消息中的退订关键词并更新内部联系人偏好。
  • 抑制向已退订收件人发送消息,避免重复出现21610错误。
入站关键词处理示例:
javascript
const normalized = body.trim().toUpperCase();
const isStop = ["STOP", "STOPALL", "UNSUBSCRIBE", "CANCEL", "END", "QUIT"].includes(normalized);
const isStart = ["START", "YES", "UNSTOP"].includes(normalized);

if (isStop) {
  // 在数据库中标记用户已退订
}
if (isStart) {
  // 在数据库中标记用户已重新订阅
}
发送消息前,预先检查数据库中的退订状态。如果仍触发Twilio阻止,处理错误码
21610

Messaging Services: sender pools, geo-matching, sticky sender

消息服务:发送方池、地理匹配、固定发送方

Use a Messaging Service to:
  • avoid hardcoding
    From
  • rotate senders safely
  • enable geo-matching (local presence)
  • enable sticky sender (consistent From per recipient)
Create a service (CLI):
bash
twilio api:messaging:v1:services:create \
  --friendly-name "prod-notifications" \
  --status-callback "https://api.example.com/twilio/status"
Add a phone number to the service:
bash
twilio api:messaging:v1:services:phone-numbers:create \
  --service-sid YOUR_MG_SID \
  --phone-number-sid PN0123456789abcdef0123456789abcdef
Enable sticky sender / geo-match (via API; CLI coverage varies by version):
bash
twilio api:messaging:v1:services:update \
  --sid YOUR_MG_SID \
  --sticky-sender true \
  --area-code-geomatch true

使用消息服务可:
  • 避免硬编码
    From
    号码
  • 安全轮换发送方
  • 启用地理匹配(本地号码适配)
  • 启用固定发送方(对同一收件人使用一致的发送方)
创建服务(CLI方式):
bash
twilio api:messaging:v1:services:create \
  --friendly-name "prod-notifications" \
  --status-callback "https://api.example.com/twilio/status"
向服务添加电话号码:
bash
twilio api:messaging:v1:services:phone-numbers:create \
  --service-sid YOUR_MG_SID \
  --phone-number-sid PN0123456789abcdef0123456789abcdef
启用固定发送方/地理匹配(通过API;CLI支持因版本而异):
bash
twilio api:messaging:v1:services:update \
  --sid YOUR_MG_SID \
  --sticky-sender true \
  --area-code-geomatch true

A2P 10DLC operational checks (US)

A2P 10DLC操作检查(美国)

What to enforce in code:
  • If sending to US numbers (
    +1...
    ) from US long codes:
    • ensure the sender is associated with an approved A2P campaign
    • ensure message content matches campaign use case (avoid content drift)
  • Monitor filtering:
    • rising
      30003
      /
      30005
      and
      undelivered
      rates
    • carrier violations and spam flags
Twilio Console is the source of truth for registration state; in CI/CD, treat campaign IDs and service SIDs as config.

代码中需强制执行的规则:
  • 若向美国号码(
    +1...
    )发送消息且使用美国长码:
    • 确保发送方关联已批准的A2P活动
    • 确保消息内容与活动用例匹配(避免内容偏离)
  • 监控过滤情况:
    • 30003
      /
      30005
      错误和
      undelivered
      率上升
    • 运营商违规和垃圾邮件标记
Twilio控制台是注册状态的权威来源;在CI/CD中,将活动ID和服务SID视为配置项。

Short codes and toll-free

短码与免费电话

  • Short code: high throughput, best deliverability; long lead time.
  • Toll-free: good for US/CA; verification required for scale.
Implementation is identical at API level; difference is provisioning and compliance.

  • 短码:高吞吐量、最佳送达率;配置周期长。
  • 免费电话:适用于美国/加拿大;大规模发送需验证。
API层面实现方式相同;差异在于配置和合规要求。

Webhook security: signature validation and URL correctness

Webhook安全:签名验证与URL正确性

Signature validation is brittle if:
  • you validate against the wrong URL (must match the public URL Twilio used)
  • you have proxies altering scheme/host
  • you parse body differently than Twilio expects
If behind a reverse proxy (ALB/NGINX), reconstruct the public URL using forwarded headers carefully, or hardcode the known public URL per route.

以下情况会导致签名验证失败:
  • 验证使用错误的URL(必须与Twilio使用的公开URL完全一致)
  • 代理修改了协议/主机
  • 解析请求体的方式与Twilio不同
若在反向代理(ALB/NGINX)后,需通过转发头小心重构公开URL,或为每个路由硬编码已知的公开URL。

Command Reference

命令参考

Twilio CLI (5.16.0)

Twilio CLI(5.16.0)

Authentication / environment

认证/环境配置

bash
twilio login
twilio profiles:list
twilio profiles:use <profile>
Set env vars for a single command:
bash
TWILIO_ACCOUNT_SID=AC... TWILIO_AUTH_TOKEN=... twilio api:core:accounts:fetch --sid AC...
bash
twilio login
twilio profiles:list
twilio profiles:use <profile>
为单个命令设置环境变量:
bash
TWILIO_ACCOUNT_SID=AC... TWILIO_AUTH_TOKEN=... twilio api:core:accounts:fetch --sid AC...

Send a message (CLI)

发送消息(CLI)

Twilio CLI has a
twilio api:core:messages:create
command (core API). Common flags:
bash
twilio api:core:messages:create \
  --to "+14155550123" \
  --from "+14155552671" \
  --body "Deploy complete." \
  --status-callback "https://api.example.com/twilio/status" \
  --provide-feedback true \
  --max-price 0.015 \
  --application-sid AP0123456789abcdef0123456789abcdef
Notes on flags:
  • --to
    (required): destination E.164.
  • --from
    : sender number (E.164). Prefer Messaging Service instead.
  • --messaging-service-sid MG...
    : use service; mutually exclusive with
    --from
    .
  • --body
    : SMS text.
  • --media-url
    : repeatable; MMS media URL(s).
  • --status-callback
    : webhook for status updates.
  • --provide-feedback
    : request carrier feedback (not always available).
  • --max-price
    : cap price (USD) for message; may cause failures if too low.
  • --application-sid
    : for TwiML app association (rare for Messaging).
MMS example:
bash
twilio api:core:messages:create \
  --to "+14155550123" \
  --messaging-service-sid YOUR_MG_SID \
  --body "Photo" \
  --media-url "https://cdn.example.com/a.png" \
  --media-url "https://cdn.example.com/b.jpg"
Fetch message:
bash
twilio api:core:messages:fetch --sid SM0123456789abcdef0123456789abcdef
List messages (filters vary; core ones):
bash
twilio api:core:messages:list --limit 50
twilio api:core:messages:list --to "+14155550123" --limit 20
twilio api:core:messages:list --from "+14155552671" --limit 20
Delete message record (rare; mostly for cleanup/testing):
bash
twilio api:core:messages:remove --sid SM0123456789abcdef0123456789abcdef
Twilio CLI提供
twilio api:core:messages:create
命令(核心API)。常用参数:
bash
twilio api:core:messages:create \
  --to "+14155550123" \
  --from "+14155552671" \
  --body "Deploy complete." \
  --status-callback "https://api.example.com/twilio/status" \
  --provide-feedback true \
  --max-price 0.015 \
  --application-sid AP0123456789abcdef0123456789abcdef
参数说明:
  • --to
    (必填):目标号码,E.164格式。
  • --from
    :发送方号码(E.164格式)。优先使用消息服务。
  • --messaging-service-sid MG...
    :使用消息服务;与
    --from
    互斥。
  • --body
    :SMS文本内容。
  • --media-url
    :可重复使用;MMS媒体文件URL。
  • --status-callback
    :状态更新Webhook。
  • --provide-feedback
    :请求运营商反馈(并非所有场景支持)。
  • --max-price
    :消息价格上限(美元);设置过低可能导致发送失败。
  • --application-sid
    :关联TwiML应用(消息服务场景中很少使用)。
MMS示例:
bash
twilio api:core:messages:create \
  --to "+14155550123" \
  --messaging-service-sid YOUR_MG_SID \
  --body "Photo" \
  --media-url "https://cdn.example.com/a.png" \
  --media-url "https://cdn.example.com/b.jpg"
获取消息详情:
bash
twilio api:core:messages:fetch --sid SM0123456789abcdef0123456789abcdef
列出消息(过滤条件多样;核心条件):
bash
twilio api:core:messages:list --limit 50
twilio api:core:messages:list --to "+14155550123" --limit 20
twilio api:core:messages:list --from "+14155552671" --limit 20
删除消息记录(罕见;主要用于清理/测试):
bash
twilio api:core:messages:remove --sid SM0123456789abcdef0123456789abcdef

Messaging Services

消息服务

Create:
bash
twilio api:messaging:v1:services:create \
  --friendly-name "prod-notifications" \
  --status-callback "https://api.example.com/twilio/status"
Update:
bash
twilio api:messaging:v1:services:update \
  --sid YOUR_MG_SID \
  --friendly-name "prod-notifications" \
  --status-callback "https://api.example.com/twilio/status" \
  --inbound-request-url "https://api.example.com/twilio/inbound" \
  --inbound-method POST
List:
bash
twilio api:messaging:v1:services:list --limit 50
Fetch:
bash
twilio api:messaging:v1:services:fetch --sid YOUR_MG_SID
Phone numbers attached to a service:
bash
twilio api:messaging:v1:services:phone-numbers:list \
  --service-sid YOUR_MG_SID \
  --limit 50
Attach a number:
bash
twilio api:messaging:v1:services:phone-numbers:create \
  --service-sid YOUR_MG_SID \
  --phone-number-sid PN0123456789abcdef0123456789abcdef
Remove a number from service:
bash
twilio api:messaging:v1:services:phone-numbers:remove \
  --service-sid YOUR_MG_SID \
  --sid PN0123456789abcdef0123456789abcdef
创建服务:
bash
twilio api:messaging:v1:services:create \
  --friendly-name "prod-notifications" \
  --status-callback "https://api.example.com/twilio/status"
更新服务:
bash
twilio api:messaging:v1:services:update \
  --sid YOUR_MG_SID \
  --friendly-name "prod-notifications" \
  --status-callback "https://api.example.com/twilio/status" \
  --inbound-request-url "https://api.example.com/twilio/inbound" \
  --inbound-method POST
列出服务:
bash
twilio api:messaging:v1:services:list --limit 50
获取服务详情:
bash
twilio api:messaging:v1:services:fetch --sid YOUR_MG_SID
查看服务关联的电话号码:
bash
twilio api:messaging:v1:services:phone-numbers:list \
  --service-sid YOUR_MG_SID \
  --limit 50
关联电话号码:
bash
twilio api:messaging:v1:services:phone-numbers:create \
  --service-sid YOUR_MG_SID \
  --phone-number-sid PN0123456789abcdef0123456789abcdef
从服务中移除电话号码:
bash
twilio api:messaging:v1:services:phone-numbers:remove \
  --service-sid YOUR_MG_SID \
  --sid PN0123456789abcdef0123456789abcdef

Incoming phone numbers (to find PN SIDs)

呼入电话号码(查找PN SID)

List numbers:
bash
twilio api:core:incoming-phone-numbers:list --limit 50
Fetch:
bash
twilio api:core:incoming-phone-numbers:fetch --sid PN0123456789abcdef0123456789abcdef
Update webhook on a number (if not using Messaging Service inbound URL):
bash
twilio api:core:incoming-phone-numbers:update \
  --sid PN0123456789abcdef0123456789abcdef \
  --sms-url "https://api.example.com/twilio/inbound" \
  --sms-method POST \
  --sms-fallback-url "https://api.example.com/twilio/fallback" \
  --sms-fallback-method POST \
  --status-callback "https://api.example.com/twilio/status"

列出号码:
bash
twilio api:core:incoming-phone-numbers:list --limit 50
获取号码详情:
bash
twilio api:core:incoming-phone-numbers:fetch --sid PN0123456789abcdef0123456789abcdef
更新号码的Webhook(若未使用消息服务入站URL):
bash
twilio api:core:incoming-phone-numbers:update \
  --sid PN0123456789abcdef0123456789abcdef \
  --sms-url "https://api.example.com/twilio/inbound" \
  --sms-method POST \
  --sms-fallback-url "https://api.example.com/twilio/fallback" \
  --sms-fallback-method POST \
  --status-callback "https://api.example.com/twilio/status"

Configuration Reference

配置参考

Environment variables

环境变量

Recommended variables:
  • TWILIO_ACCOUNT_SID
  • TWILIO_AUTH_TOKEN
  • TWILIO_MESSAGING_SERVICE_SID
    (preferred)
  • TWILIO_FROM_NUMBER
    (only if not using service)
  • TWILIO_STATUS_CALLBACK_URL
  • TWILIO_INBOUND_WEBHOOK_PUBLIC_URL
    (for signature validation)
推荐配置的变量:
  • TWILIO_ACCOUNT_SID
  • TWILIO_AUTH_TOKEN
  • TWILIO_MESSAGING_SERVICE_SID
    (优先推荐)
  • TWILIO_FROM_NUMBER
    (仅当不使用消息服务时配置)
  • TWILIO_STATUS_CALLBACK_URL
  • TWILIO_INBOUND_WEBHOOK_PUBLIC_URL
    (用于签名验证)

Node.js
.env

Node.js
.env
文件

Path:
/srv/app/.env
(Linux) or project root for local dev.
dotenv
TWILIO_ACCOUNT_SID=YOUR_ACCOUNT_SID
TWILIO_AUTH_TOKEN=0123456789abcdef0123456789abcdef
TWILIO_MESSAGING_SERVICE_SID=YOUR_MG_SID
TWILIO_STATUS_CALLBACK_URL=https://api.example.com/twilio/status
TWILIO_INBOUND_WEBHOOK_PUBLIC_URL=https://api.example.com/twilio/inbound
Load with
dotenv
:
bash
npm install dotenv@16.4.5
javascript
import "dotenv/config";
路径:Linux为
/srv/app/.env
,本地开发为项目根目录。
dotenv
TWILIO_ACCOUNT_SID=YOUR_ACCOUNT_SID
TWILIO_AUTH_TOKEN=0123456789abcdef0123456789abcdef
TWILIO_MESSAGING_SERVICE_SID=YOUR_MG_SID
TWILIO_STATUS_CALLBACK_URL=https://api.example.com/twilio/status
TWILIO_INBOUND_WEBHOOK_PUBLIC_URL=https://api.example.com/twilio/inbound
使用
dotenv
加载:
bash
npm install dotenv@16.4.5
javascript
import "dotenv/config";

systemd unit (production)

systemd单元文件(生产环境)

Path:
/etc/systemd/system/messaging-api.service
ini
[Unit]
Description=Messaging API
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=messaging
Group=messaging
WorkingDirectory=/srv/messaging-api
EnvironmentFile=/etc/messaging-api/env
ExecStart=/usr/bin/node /srv/messaging-api/dist/server.js
Restart=on-failure
RestartSec=2
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/srv/messaging-api /var/log/messaging-api
AmbientCapabilities=
CapabilityBoundingSet=

[Install]
WantedBy=multi-user.target
Secrets file path:
/etc/messaging-api/env
(chmod 600)
bash
sudo install -m 600 -o root -g root /dev/null /etc/messaging-api/env
Example
/etc/messaging-api/env
:
bash
TWILIO_ACCOUNT_SID=YOUR_ACCOUNT_SID
TWILIO_AUTH_TOKEN=0123456789abcdef0123456789abcdef
TWILIO_MESSAGING_SERVICE_SID=YOUR_MG_SID
TWILIO_STATUS_CALLBACK_URL=https://api.example.com/twilio/status
TWILIO_INBOUND_WEBHOOK_PUBLIC_URL=https://api.example.com/twilio/inbound
PORT=3000
路径:
/etc/systemd/system/messaging-api.service
ini
[Unit]
Description=Messaging API
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=messaging
Group=messaging
WorkingDirectory=/srv/messaging-api
EnvironmentFile=/etc/messaging-api/env
ExecStart=/usr/bin/node /srv/messaging-api/dist/server.js
Restart=on-failure
RestartSec=2
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/srv/messaging-api /var/log/messaging-api
AmbientCapabilities=
CapabilityBoundingSet=

[Install]
WantedBy=multi-user.target
密钥文件路径:
/etc/messaging-api/env
(权限设置为600)
bash
sudo install -m 600 -o root -g root /dev/null /etc/messaging-api/env
/etc/messaging-api/env
示例:
bash
TWILIO_ACCOUNT_SID=YOUR_ACCOUNT_SID
TWILIO_AUTH_TOKEN=0123456789abcdef0123456789abcdef
TWILIO_MESSAGING_SERVICE_SID=YOUR_MG_SID
TWILIO_STATUS_CALLBACK_URL=https://api.example.com/twilio/status
TWILIO_INBOUND_WEBHOOK_PUBLIC_URL=https://api.example.com/twilio/inbound
PORT=3000

NGINX reverse proxy (webhook endpoints)

NGINX反向代理(Webhook端点)

Path:
/etc/nginx/conf.d/messaging-api.conf
nginx
server {
  listen 443 ssl http2;
  server_name api.example.com;

  ssl_certificate     /etc/letsencrypt/live/api.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;

  location /twilio/ {
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_read_timeout 10s;
  }
}
Important: signature validation depends on the URL Twilio used; ensure your app uses the external URL, not
http://127.0.0.1
.

路径:
/etc/nginx/conf.d/messaging-api.conf
nginx
server {
  listen 443 ssl http2;
  server_name api.example.com;

  ssl_certificate     /etc/letsencrypt/live/api.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;

  location /twilio/ {
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_read_timeout 10s;
  }
}
注意:签名验证依赖Twilio使用的URL;确保你的应用使用外部URL,而非
http://127.0.0.1

Integration Patterns

集成模式

Pattern: Outbound notifications with delivery-driven fallback

模式:带送达驱动备用方案的出站通知

Pipeline:
  1. Send SMS with
    statusCallback
    .
  2. On callback:
    • if
      delivered
      : mark success.
    • if
      undelivered
      /
      failed
      : enqueue email via SendGrid or push notification.
Pseudo-architecture:
  • API service: accepts “notify user” request.
  • Queue: stores send jobs.
  • Worker: sends Twilio message.
  • Webhook service: processes status callbacks and triggers fallback.
Example (status callback → SQS):
javascript
// on /twilio/status
if (status === "undelivered" || status === "failed") {
  await sqs.sendMessage({
    QueueUrl: process.env.FALLBACK_QUEUE_URL,
    MessageBody: JSON.stringify({ messageSid, to: req.body.To, reason: req.body.ErrorCode }),
  });
}
流程:
  1. 发送带
    statusCallback
    的SMS。
  2. 回调处理:
    • 若为
      delivered
      :标记成功。
    • 若为
      undelivered
      /
      failed
      :通过SendGrid或推送通知加入邮件队列。
伪架构:
  • API服务:接收“通知用户”请求。
  • 队列:存储发送任务。
  • 工作进程:发送Twilio消息。
  • Webhook服务:处理状态回调并触发备用方案。
示例(状态回调→SQS):
javascript
// 在/twilio/status端点
if (status === "undelivered" || status === "failed") {
  await sqs.sendMessage({
    QueueUrl: process.env.FALLBACK_QUEUE_URL,
    MessageBody: JSON.stringify({ messageSid, to: req.body.To, reason: req.body.ErrorCode }),
  });
}

Pattern: Two-way support with ticketing

模式:带工单系统的双向支持

Inbound SMS:
  • Validate signature.
  • Normalize sender (
    From
    ) and map to customer.
  • Create ticket in Jira/ServiceNow/Zendesk.
  • Reply with ticket ID via TwiML.
Example TwiML reply:
xml
<Response>
  <Message>Your ticket INC-2048 is open. Reply HELP for options.</Message>
</Response>
入站SMS:
  • 验证签名。
  • 标准化发送方(
    From
    )并映射到客户。
  • 在Jira/ServiceNow/Zendesk中创建工单。
  • 通过TwiML回复工单ID。
TwiML回复示例:
xml
<Response>
  <Message>Your ticket INC-2048 is open. Reply HELP for options.</Message>
</Response>

Pattern: Keyword-based workflows (HELP/STOP/START + custom)

模式:基于关键词的工作流(HELP/STOP/START + 自定义)

  • HELP
    : return support URL and contact.
  • STOP
    : update internal preference store (Twilio also blocks).
  • Custom keywords:
    STATUS <id>
    ,
    ONCALL
    ,
    ACK <incident>
    .
Ensure parsing is robust and case-insensitive; log raw inbound payload for audit.
  • HELP
    :返回支持URL和联系方式。
  • STOP
    :更新内部偏好存储(Twilio也会阻止发送)。
  • 自定义关键词:
    STATUS <id>
    ,
    ONCALL
    ,
    ACK <incident>
确保解析逻辑健壮且不区分大小写;记录原始入站载荷用于审计。

Pattern: Multi-region sending with geo-matching

模式:带地理匹配的多区域发送

  • Use a single Messaging Service with:
    • sender pool across regions
    • geo-matching enabled
  • For compliance, route by recipient country:
    • US/CA: toll-free or A2P 10DLC long code
    • UK: alphanumeric sender ID (if supported) or local number
    • India: DLT constraints (outside scope here; treat as separate compliance module)
  • 使用单个消息服务,包含:
    • 跨区域的发送方池
    • 启用地理匹配
  • 合规性要求:按收件人国家路由:
    • 美国/加拿大:免费电话或A2P 10DLC长码
    • 英国:字母数字发送方ID(若支持)或本地号码
    • 印度:DLT约束(超出本文范围;视为独立合规模块)

Pattern: Idempotent send API

模式:幂等发送API

If your upstream retries, you must avoid duplicate SMS.
Approach:
  • Accept
    idempotencyKey
    from caller.
  • Store mapping
    idempotencyKey -> MessageSid
    .
  • If key exists, return existing
    MessageSid
    .
Example DB constraint:
  • Unique index on
    idempotency_key
    .

若上游系统重试,必须避免重复发送SMS。
实现方式:
  • 接收调用方传入的
    idempotencyKey
  • 存储
    idempotencyKey -> MessageSid
    的映射。
  • 若密钥已存在,返回已有的
    MessageSid
数据库约束示例:
  • idempotency_key
    创建唯一索引。

Error Handling & Troubleshooting

错误处理与故障排除

Handle errors at two layers:
  • REST API call errors (synchronous)
  • Status callback errors (asynchronous delivery failures)
Below are common Twilio errors with root cause and fix.
在两个层面处理错误:
  • REST API调用错误(同步)
  • 状态回调错误(异步送达失败)
以下是常见Twilio错误的根因和修复方案。

1) 21211 — invalid
To
number

1) 21211 — 无效
To
号码

Error text (typical):
Twilio could not find a Channel with the specified From address
or:
The 'To' number +1415555 is not a valid phone number.
Root causes:
  • Not E.164 formatted.
  • Contains spaces/parentheses.
  • Invalid country code.
Fix:
  • Normalize to E.164 before sending.
  • Validate with libphonenumber.
  • Reject at API boundary with clear error.
典型错误文本:
Twilio could not find a Channel with the specified From address
或:
The 'To' number +1415555 is not a valid phone number.
根因:
  • 未使用E.164格式。
  • 包含空格/括号。
  • 无效国家代码。
修复:
  • 发送前标准化为E.164格式。
  • 使用libphonenumber验证。
  • 在API边界拒绝并返回清晰错误。

2) 20003 — authentication error

2) 20003 — 认证错误

Error text:
Authenticate
or:
Unable to create record: Authenticate
Root causes:
  • Wrong
    TWILIO_AUTH_TOKEN
    .
  • Using test credentials against live endpoint or vice versa.
  • Account SID mismatch.
Fix:
  • Verify env vars in runtime.
  • Rotate token if leaked.
  • In CI, ensure correct secret scope.
错误文本:
Authenticate
或:
Unable to create record: Authenticate
根因:
  • TWILIO_AUTH_TOKEN
    错误。
  • 测试凭证用于生产环境或反之。
  • 账户SID不匹配。
修复:
  • 验证运行时环境变量。
  • 泄露后轮换令牌。
  • 在CI中确保正确的密钥范围。

3) 20429 — rate limit exceeded

3) 20429 — 超出速率限制

Error text:
Too Many Requests
Root causes:
  • Bursty sends exceeding Twilio or Messaging Service limits.
  • Excessive API polling.
Fix:
  • Implement client-side rate limiting and backoff (exponential with jitter).
  • Batch sends via queue workers.
  • Use Messaging Service / short code for higher throughput.
错误文本:
Too Many Requests
根因:
  • 突发发送超出Twilio或消息服务限制。
  • 过度API轮询。
修复:
  • 实现客户端速率限制和退避(指数退避加抖动)。
  • 通过队列工作进程批量发送。
  • 使用消息服务/短码提升吞吐量。

4) 21610 — recipient opted out

4) 21610 — 收件人已退订

Error text:
The message From/To pair violates a blacklist rule.
Root causes:
  • Recipient replied STOP (or carrier-level block).
  • Your system keeps retrying.
Fix:
  • Suppress sends to opted-out recipients in your DB.
  • Provide opt-in flow (START).
  • Do not retry 21610; treat as terminal.
错误文本:
The message From/To pair violates a blacklist rule.
根因:
  • 收件人回复STOP(或运营商级阻止)。
  • 系统持续重试。
修复:
  • 在数据库中抑制向已退订收件人发送消息。
  • 提供重新订阅流程(START)。
  • 不要重试21610错误;视为终端错误。

5) 30003 — Unreachable destination handset / carrier violation

5) 30003 — 目标手机无法到达/运营商违规

Error text (common):
Unreachable destination handset
Root causes:
  • Device off/out of coverage.
  • Carrier filtering.
  • Invalid or deactivated number.
Fix:
  • Treat as non-retryable after limited attempts.
  • Trigger fallback channel.
  • Monitor spikes by carrier/country; check A2P registration and content.
常见错误文本:
Unreachable destination handset
根因:
  • 设备关机/无信号。
  • 运营商过滤。
  • 号码无效或已停用。
修复:
  • 有限次数尝试后视为不可重试。
  • 触发备用渠道。
  • 按运营商/国家监控峰值;检查A2P注册和内容。

6) 30005 — Unknown destination handset

6) 30005 — 未知目标手机

Error text:
Unknown destination handset
Root causes:
  • Number not assigned.
  • Porting issues.
Fix:
  • Mark number invalid after repeated failures.
  • Ask user to update phone number.
错误文本:
Unknown destination handset
根因:
  • 号码未分配。
  • 号码转移问题。
修复:
  • 多次失败后标记号码无效。
  • 要求用户更新电话号码。

7) 21614 — 'To' is not a valid mobile number (MMS)

7) 21614 — 'To'号码不支持MMS

Error text:
'To' number is not a valid mobile number
Root causes:
  • Attempting MMS to a number/carrier that doesn’t support MMS.
  • Landline.
Fix:
  • Detect MMS capability; fallback to SMS with link.
  • Use Lookup (Twilio Lookup API) if you must preflight (cost tradeoff).
错误文本:
'To' number is not a valid mobile number
根因:
  • 尝试向不支持MMS的号码/运营商发送MMS。
  • 固定电话。
修复:
  • 检测MMS能力;回退到带链接的SMS。
  • 若必须预检查,使用Twilio Lookup API(权衡成本)。

8) 21606 — From number not capable of sending SMS

8) 21606 — 发送方号码无法发送SMS

Error text:
The From phone number +14155552671 is not a valid, SMS-capable inbound phone number or short code for your account.
Root causes:
  • Using a voice-only number.
  • Number not owned by your account.
  • Wrong sender configured.
Fix:
  • Use Messaging Service with verified senders.
  • Confirm number capabilities in Console or via IncomingPhoneNumbers API.
错误文本:
The From phone number +14155552671 is not a valid, SMS-capable inbound phone number or short code for your account.
根因:
  • 使用仅支持语音的号码。
  • 号码不属于你的账户。
  • 发送方配置错误。
修复:
  • 使用带已验证发送方的消息服务。
  • 在控制台或通过IncomingPhoneNumbers API确认号码能力。

9) Webhook signature failures (your logs)

9) Webhook签名失败(日志中)

Typical app log:
Invalid signature
Root causes:
  • Validating against wrong URL (http vs https, host mismatch, path mismatch).
  • Proxy rewrites.
  • Body parsing differences.
Fix:
  • Validate against the exact public URL configured in Twilio.
  • Ensure
    express.urlencoded()
    is used for form payloads.
  • If behind proxy, set
    app.set('trust proxy', true)
    and reconstruct URL carefully.
典型应用日志:
Invalid signature
根因:
  • 验证使用错误URL(http与https、主机不匹配、路径不匹配)。
  • 代理重写。
  • 请求体解析方式差异。
修复:
  • 使用Twilio中配置的精确公开URL进行验证。
  • 确保表单载荷使用
    express.urlencoded()
    解析。
  • 若在代理后,设置
    app.set('trust proxy', true)
    并小心重构URL。

10) Status callback not firing

10) 状态回调未触发

Symptoms:
  • Message shows delivered in console but your system never receives callback.
Root causes:
  • statusCallback
    not set on message or service.
  • Callback URL returns non-2xx; Twilio retries then gives up.
  • Firewall blocks Twilio IPs (don’t IP allowlist unless you maintain Twilio ranges).
Fix:
  • Set status callback at Messaging Service level.
  • Ensure endpoint returns 2xx quickly.
  • Log request bodies and response codes; add metrics.

症状:
  • 控制台显示消息已送达,但系统从未收到回调。
根因:
  • 消息或服务未设置
    statusCallback
  • 回调URL返回非2xx;Twilio重试后放弃。
  • 防火墙阻止Twilio IP(除非维护Twilio IP范围,否则不要IP白名单)。
修复:
  • 在消息服务层面设置状态回调。
  • 确保端点快速返回2xx。
  • 记录请求体和响应码;添加指标。

Security Hardening

安全加固

Secrets handling

密钥处理

  • Do not store
    TWILIO_AUTH_TOKEN
    in repo.
  • Use secret manager + short-lived deployment injection.
  • Rotate tokens on incident; treat as high-impact credential.
  • 不要在代码库中存储
    TWILIO_AUTH_TOKEN
  • 使用密钥管理器+短期部署注入。
  • 事件发生后轮换令牌;视为高影响凭证。

Webhook validation (mandatory)

Webhook验证(强制要求)

  • Validate
    X-Twilio-Signature
    for inbound and status callbacks.
  • Reject invalid signatures with 403.
  • Log minimal metadata; avoid logging full message bodies if sensitive.
  • 验证入站和状态回调的
    X-Twilio-Signature
  • 无效签名返回403。
  • 记录最小元数据;敏感内容避免记录完整消息体。

TLS and transport

TLS与传输

  • Enforce HTTPS for all webhook endpoints.
  • Disable TLS 1.0/1.1; prefer TLS 1.2+.
  • If using NGINX, follow CIS NGINX Benchmark guidance for strong ciphers (adapt to your org baseline).
  • 所有Webhook端点强制使用HTTPS。
  • 禁用TLS 1.0/1.1;优先使用TLS 1.2+。
  • 若使用NGINX,遵循CIS NGINX基准指南配置强加密套件(适配组织基线)。

Request handling

请求处理

  • Limit request body size (protect against abuse):
    • Express:
      express.urlencoded({ limit: "64kb", extended: false })
  • Apply rate limiting on webhook endpoints (careful: Twilio retries; don’t block legitimate retries).
  • Use WAF rules to block obvious abuse, but do not rely on IP allowlists.
  • 限制请求体大小(防止滥用):
    • Express:
      express.urlencoded({ limit: "64kb", extended: false })
  • Webhook端点应用速率限制(注意:Twilio会重试;不要阻止合法重试)。
  • 使用WAF规则阻止明显滥用,但不要依赖IP白名单。

Data minimization

数据最小化

  • Store only what you need:
    • MessageSid
      ,
      To
      ,
      From
      , timestamps, status, error codes
  • If storing message content, encrypt at rest and restrict access.
  • 仅存储必要数据:
    • MessageSid
      ,
      To
      ,
      From
      , 时间戳, 状态, 错误码
  • 若存储消息内容,静态加密并限制访问。

OS/service hardening (Linux)

OS/服务加固(Linux)

  • systemd hardening flags (see unit file above).
  • Run as non-root user.
  • Follow CIS Linux Benchmarks (Ubuntu 22.04 / RHEL/Fedora equivalents):
    • restrict file permissions on env files (
      chmod 600
      )
    • audit access to secrets
    • enable automatic security updates where appropriate

  • systemd加固标志(见上方单元文件)。
  • 以非root用户运行。
  • 遵循CIS Linux基准(Ubuntu 22.04 / RHEL/Fedora等效版本):
    • 限制环境文件权限(
      chmod 600
    • 审计密钥访问
    • 适当启用自动安全更新

Performance Tuning

性能调优

Reduce webhook latency (p95)

降低Webhook延迟(p95)

Goal: respond to Twilio webhooks in < 200ms p95.
Actions:
  • Do not call external services synchronously in webhook handler.
  • Enqueue work (SQS/Kafka/Redis) and return 204/200 immediately.
  • Pre-parse and validate quickly; avoid heavy logging.
Expected impact:
  • Before: webhook handler does DB + ticket creation → 1–3s p95, retries increase load.
  • After: enqueue only → <100–200ms p95, fewer retries, lower Twilio webhook backlog.
目标:Twilio Webhook响应时间p95 < 200ms。
措施:
  • Webhook处理程序中不要同步调用外部服务。
  • 将工作加入队列(SQS/Kafka/Redis)并立即返回204/200。
  • 快速预解析和验证;避免大量日志。
预期影响:
  • 优化前:Webhook处理程序执行DB+工单创建→p95 1–3s,重试增加负载。
  • 优化后:仅加入队列→p95 <100–200ms,重试减少,Twilio Webhook积压降低。

Throughput scaling for outbound sends

出站发送吞吐量扩展

  • Use worker pool with concurrency control.
  • Implement token bucket rate limiting per Messaging Service / sender type.
  • Prefer Messaging Service with appropriate sender (short code/toll-free) for higher throughput.
Expected impact:
  • Prevents 20429 spikes and reduces carrier filtering due to bursty patterns.
  • 使用带并发控制的工作进程池。
  • 为消息服务/发送方类型实现令牌桶速率限制。
  • 优先使用合适发送方(短码/免费电话)的消息服务提升吞吐量。
预期影响:
  • 防止20429错误峰值,减少突发模式导致的运营商过滤。

Cost optimization

成本优化

  • Use Messaging Service geo-matching to reduce cross-region costs where applicable.
  • Avoid MMS when SMS + link suffices.
  • Use
    maxPrice
    carefully; too low increases failures.
  • 使用消息服务地理匹配减少跨区域成本(适用场景)。
  • 能用SMS+链接替代时避免MMS。
  • 谨慎使用
    maxPrice
    ;设置过低会增加失败率。

Encoding and segmentation

编码与分段

  • GSM-7 vs UCS-2 affects segment count and cost.
  • If you send non-GSM characters (e.g., emoji, some accented chars), messages may switch to UCS-2 and segment at 70 chars.
Mitigation:
  • Normalize content where acceptable.
  • Keep messages short; move details to links.

  • GSM-7与UCS-2影响分段数量和成本。
  • 若发送非GSM字符(如表情符号、部分带重音字符),消息可能切换为UCS-2并按70字符分段。
缓解措施:
  • 可接受的情况下标准化内容。
  • 保持消息简短;详细内容放在链接中。

Advanced Topics

高级主题

Idempotency across retries and deploys

重试与部署中的幂等性

Twilio REST
messages.create
is not idempotent by default. If your worker retries after a timeout, you may double-send.
Mitigations:
  • Use an application-level idempotency key stored in DB.
  • On timeout, query by your own correlation ID (store in
    statusCallback
    query string or in
    body
    is not safe). Better:
    • store job ID → message SID mapping once created
    • if create call times out, attempt to detect if message was created by checking Twilio logs is unreliable; prefer conservative “may have sent” handling and alert.
Twilio REST
messages.create
默认不幂等。若工作进程超时后重试,可能重复发送。
缓解方案:
  • 使用应用层面的幂等密钥存储在数据库中。
  • 超时后,通过查询自己的关联ID检测消息是否已创建(不要在
    statusCallback
    查询字符串或
    body
    中存储;不可靠)。更优方案:
    • 存储任务ID→消息SID的映射
    • 创建调用超时后,检测消息是否已创建不可靠;优先采用保守的“可能已发送”处理并告警。

Handling duplicate status callbacks

处理重复状态回调

Twilio may send multiple callbacks for the same status or out of order.
  • Store transitions with monotonic state machine:
    • queued
      <
      sending
      <
      sent
      <
      delivered
    • terminal failures:
      undelivered
      /
      failed
  • If you receive
    delivered
    after
    undelivered
    , keep both but treat delivered as final if timestamp is later (rare but possible due to carrier reporting quirks).
Twilio可能为同一状态发送多个回调或乱序发送。
  • 使用单调状态机存储转换:
    • queued
      <
      sending
      <
      sent
      <
      delivered
    • 终端失败:
      undelivered
      /
      failed
  • 若先收到
    undelivered
    后收到
    delivered
    ,保留两者但以时间戳较晚的
    delivered
    为最终状态(罕见,因运营商报告 quirks)。

Multi-tenant systems

多租户系统

If you operate multiple Twilio subaccounts:
  • Store per-tenant Account SID/Auth Token (or use API Keys).
  • Validate webhooks per tenant:
    • signature validation uses the tenant’s auth token
    • route by
      To
      number or by dedicated webhook URL per tenant
若运营多个Twilio子账户:
  • 按租户存储账户SID/认证令牌(或使用API密钥)。
  • 按租户验证Webhook:
    • 签名验证使用租户的认证令牌
    • To
      号码或专用Webhook URL路由

Media URL security for MMS

MMS媒体URL安全

  • Twilio fetches media from your URL; it must be reachable.
  • If using signed URLs:
    • TTL must exceed Twilio fetch window (minutes to tens of minutes; be conservative).
  • Do not require cookies.
  • If you require auth headers, Twilio cannot provide them; use pre-signed URLs.
  • Twilio会从你的URL获取媒体;必须可访问。
  • 若使用签名URL:
    • 有效期必须超过Twilio获取窗口(数分钟到数十分钟;保守设置)。
  • 不要要求Cookie。
  • 若要求认证头,Twilio无法提供;使用预签名URL。

Compliance gotchas

合规陷阱

  • Do not send marketing content from unregistered A2P campaigns.
  • Maintain HELP/STOP responses and honor opt-out across channels if your policy requires it.
  • Keep audit logs for consent (timestamp, source, IP/user agent if applicable).

  • 不要从未注册的A2P活动发送营销内容。
  • 若政策要求,跨渠道维护HELP/STOP响应并尊重退订。
  • 保留同意审计日志(时间戳、来源、IP/用户代理,若适用)。

Usage Examples

使用示例

1) Production outbound SMS with Messaging Service + status tracking (Node)

1) 生产环境带消息服务+状态跟踪的出站SMS(Node.js)

Files:
  • /srv/messaging-api/src/send.js
javascript
import "dotenv/config";
import twilio from "twilio";

const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);

export async function sendDeployNotification({ to, buildId }) {
  const msg = await client.messages.create({
    messagingServiceSid: process.env.TWILIO_MESSAGING_SERVICE_SID,
    to,
    body: `Build ${buildId} deployed to prod. Reply HELP for support, STOP to opt out.`,
    statusCallback: process.env.TWILIO_STATUS_CALLBACK_URL,
    provideFeedback: true,
  });

  return { sid: msg.sid, status: msg.status };
}
Run:
bash
node -e 'import("./src/send.js").then(m=>m.sendDeployNotification({to:"+14155550123",buildId:"742"}).then(console.log))'
文件:
  • /srv/messaging-api/src/send.js
javascript
import "dotenv/config";
import twilio from "twilio";

const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);

export async function sendDeployNotification({ to, buildId }) {
  const msg = await client.messages.create({
    messagingServiceSid: process.env.TWILIO_MESSAGING_SERVICE_SID,
    to,
    body: `Build ${buildId} deployed to prod. Reply HELP for support, STOP to opt out.`,
    statusCallback: process.env.TWILIO_STATUS_CALLBACK_URL,
    provideFeedback: true,
  });

  return { sid: msg.sid, status: msg.status };
}
运行:
bash
node -e 'import("./src/send.js").then(m=>m.sendDeployNotification({to:"+14155550123",buildId:"742"}).then(console.log))'

2) Inbound SMS → create ticket → reply with TwiML (FastAPI)

2) 入站SMS→创建工单→TwiML回复(FastAPI)

  • /srv/messaging-api/app.py
python
from fastapi import FastAPI, Request, Response
from twilio.request_validator import RequestValidator
from twilio.twiml.messaging_response import MessagingResponse
import os

app = FastAPI()
validator = RequestValidator(os.environ["TWILIO_AUTH_TOKEN"])

def create_ticket(from_number: str, body: str) -> str:
    # Replace with real integration
    return "INC-2048"

@app.post("/twilio/inbound")
async def inbound(request: Request):
    form = await request.form()
    signature = request.headers.get("X-Twilio-Signature", "")
    url = os.environ["TWILIO_INBOUND_WEBHOOK_PUBLIC_URL"]

    if not validator.validate(url, dict(form), signature):
        return Response("Invalid signature", status_code=403)

    from_number = form.get("From") or ""
    body = (form.get("Body") or "").strip()

    ticket = create_ticket(from_number, body)

    resp = MessagingResponse()
    resp.message(f"Created {ticket}. Reply HELP for options. Reply STOP to opt out.")
    return Response(str(resp), media_type="text/xml")
Run:
bash
python3.11 -m venv .venv && source .venv/bin/activate
pip install fastapi==0.109.2 uvicorn==0.27.1 twilio==9.4.1 python-multipart==0.0.9
uvicorn app:app --host 0.0.0.0 --port 3000
  • /srv/messaging-api/app.py
python
from fastapi import FastAPI, Request, Response
from twilio.request_validator import RequestValidator
from twilio.twiml.messaging_response import MessagingResponse
import os

app = FastAPI()
validator = RequestValidator(os.environ["TWILIO_AUTH_TOKEN"])

def create_ticket(from_number: str, body: str) -> str:
    # 替换为实际集成逻辑
    return "INC-2048"

@app.post("/twilio/inbound")
async def inbound(request: Request):
    form = await request.form()
    signature = request.headers.get("X-Twilio-Signature", "")
    url = os.environ["TWILIO_INBOUND_WEBHOOK_PUBLIC_URL"]

    if not validator.validate(url, dict(form), signature):
        return Response("Invalid signature", status_code=403)

    from_number = form.get("From") or ""
    body = (form.get("Body") or "").strip()

    ticket = create_ticket(from_number, body)

    resp = MessagingResponse()
    resp.message(f"Created {ticket}. Reply HELP for options. Reply STOP to opt out.")
    return Response(str(resp), media_type="text/xml")
运行:
bash
python3.11 -m venv .venv && source .venv/bin/activate
pip install fastapi==0.109.2 uvicorn==0.27.1 twilio==9.4.1 python-multipart==0.0.9
uvicorn app:app --host 0.0.0.0 --port 3000

3) Status callback → metrics + fallback enqueue (Express)

3) 状态回调→指标+备用队列(Express)

  • /srv/messaging-api/src/status.js
javascript
import express from "express";
import twilio from "twilio";

const app = express();
app.use(express.urlencoded({ extended: false, limit: "64kb" }));

app.post("/twilio/status", async (req, res) => {
  const signature = req.header("X-Twilio-Signature") || "";
  const url = process.env.TWILIO_STATUS_CALLBACK_PUBLIC_URL;

  const ok = twilio.validateRequest(process.env.TWILIO_AUTH_TOKEN, signature, url, req.body);
  if (!ok) return res.status(403).send("Invalid signature");

  const { MessageSid, MessageStatus, ErrorCode, To } = req.body;

  // Example: emit metric
  console.log("twilio_status", { MessageSid, MessageStatus, ErrorCode });

  if (MessageStatus === "failed" || MessageStatus === "undelivered") {
    // enqueue fallback (pseudo)
    console.log("enqueue_fallback", { to: To, messageSid: MessageSid, reason: ErrorCode });
  }

  res.status(204).send();
});

app.listen(3000);
  • /srv/messaging-api/src/status.js
javascript
import express from "express";
import twilio from "twilio";

const app = express();
app.use(express.urlencoded({ extended: false, limit: "64kb" }));

app.post("/twilio/status", async (req, res) => {
  const signature = req.header("X-Twilio-Signature") || "";
  const url = process.env.TWILIO_STATUS_CALLBACK_PUBLIC_URL;

  const ok = twilio.validateRequest(process.env.TWILIO_AUTH_TOKEN, signature, url, req.body);
  if (!ok) return res.status(403).send("Invalid signature");

  const { MessageSid, MessageStatus, ErrorCode, To } = req.body;

  // 示例:发送指标
  console.log("twilio_status", { MessageSid, MessageStatus, ErrorCode });

  if (MessageStatus === "failed" || MessageStatus === "undelivered") {
    // 加入备用队列(伪代码)
    console.log("enqueue_fallback", { to: To, messageSid: MessageSid, reason: ErrorCode });
  }

  res.status(204).send();
});

app.listen(3000);

4) MMS with signed URL (S3 presigned) + fallback to SMS link

4) 带签名URL(S3预签名)的MMS+SMS链接回退

Pseudo-flow:
  • Generate presigned URL valid for 2 hours.
  • Send MMS with media URL.
  • If status callback returns
    21614
    or
    undelivered
    , send SMS with link.
Python snippet:
python
msg = client.messages.create(
    messaging_service_sid=os.environ["TWILIO_MESSAGING_SERVICE_SID"],
    to="+14155550123",
    body="Incident screenshot attached.",
    media_url=[presigned_url],  # TTL >= 2h
    status_callback="https://api.example.com/twilio/status",
)
Fallback SMS body:
MMS failed on your carrier. View: https://app.example.com/incidents/INC-2048
伪流程:
  • 生成有效期2小时的预签名URL。
  • 发送带媒体URL的MMS。
  • 若状态回调返回
    21614
    undelivered
    ,发送带链接的SMS。
Python代码片段:
python
msg = client.messages.create(
    messaging_service_sid=os.environ["TWILIO_MESSAGING_SERVICE_SID"],
    to="+14155550123",
    body="Incident screenshot attached.",
    media_url=[presigned_url],  # 有效期≥2h
    status_callback="https://api.example.com/twilio/status",
)
回退SMS内容:
MMS failed on your carrier. View: https://app.example.com/incidents/INC-2048

5) Opt-out aware bulk send with concurrency + suppression

5) 带并发+抑制的退订感知批量发送

  • Load recipients.
  • Filter out opted-out in DB.
  • Send with concurrency 20.
  • On 21610, mark opted-out.
Node (sketch):
javascript
import pLimit from "p-limit";
import twilio from "twilio";

const limit = pLimit(20);
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);

async function sendOne(to) {
  try {
    return await client.messages.create({
      messagingServiceSid: process.env.TWILIO_MESSAGING_SERVICE_SID,
      to,
      body: "Maintenance tonight 01:00-02:00 UTC. Reply STOP to opt out.",
      statusCallback: process.env.TWILIO_STATUS_CALLBACK_URL,
    });
  } catch (e) {
    const code = e?.code;
    if (code === 21610) {
      // mark opted out
      return null;
    }
    throw e;
  }
}

await Promise.all(recipients.map((to) => limit(() => sendOne(to))));
Install:
bash
npm install p-limit@5.0.0 twilio@4.23.0

  • 加载收件人。
  • 过滤数据库中已退订的收件人。
  • 并发数20发送。
  • 收到21610错误时标记为已退订。
Node.js(示例):
javascript
import pLimit from "p-limit";
import twilio from "twilio";

const limit = pLimit(20);
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);

async function sendOne(to) {
  try {
    return await client.messages.create({
      messagingServiceSid: process.env.TWILIO_MESSAGING_SERVICE_SID,
      to,
      body: "Maintenance tonight 01:00-02:00 UTC. Reply STOP to opt out.",
      statusCallback: process.env.TWILIO_STATUS_CALLBACK_URL,
    });
  } catch (e) {
    const code = e?.code;
    if (code === 21610) {
      // 标记为已退订
      return null;
    }
    throw e;
  }
}

await Promise.all(recipients.map((to) => limit(() => sendOne(to))));
安装依赖:
bash
npm install p-limit@5.0.0 twilio@4.23.0

Quick Reference

快速参考

TaskCommand / APIKey flags / fields
Send SMS (CLI)
twilio api:core:messages:create
--to
,
--from
or
--messaging-service-sid
,
--body
,
--status-callback
,
--provide-feedback
,
--max-price
Send MMS (CLI)
twilio api:core:messages:create
--media-url
(repeatable),
--body
Fetch message
twilio api:core:messages:fetch --sid SM...
--sid
List messages
twilio api:core:messages:list
--to
,
--from
,
--limit
Create Messaging Service
twilio api:messaging:v1:services:create
--friendly-name
,
--status-callback
Update service webhooks
twilio api:messaging:v1:services:update
--inbound-request-url
,
--inbound-method
,
--status-callback
Attach number to service
twilio api:messaging:v1:services:phone-numbers:create
--service-sid
,
--phone-number-sid
Inbound webhookHTTP POST
/twilio/inbound
Validate
X-Twilio-Signature
, parse form fields
Status callbackHTTP POST
/twilio/status
MessageSid
,
MessageStatus
,
ErrorCode

任务命令/API关键参数/字段
发送SMS(CLI)
twilio api:core:messages:create
--to
,
--from
--messaging-service-sid
,
--body
,
--status-callback
,
--provide-feedback
,
--max-price
发送MMS(CLI)
twilio api:core:messages:create
--media-url
(可重复),
--body
获取消息详情
twilio api:core:messages:fetch --sid SM...
--sid
列出消息
twilio api:core:messages:list
--to
,
--from
,
--limit
创建消息服务
twilio api:messaging:v1:services:create
--friendly-name
,
--status-callback
更新服务Webhook
twilio api:messaging:v1:services:update
--inbound-request-url
,
--inbound-method
,
--status-callback
关联号码到服务
twilio api:messaging:v1:services:phone-numbers:create
--service-sid
,
--phone-number-sid
入站WebhookHTTP POST
/twilio/inbound
验证
X-Twilio-Signature
, 解析表单字段
状态回调HTTP POST
/twilio/status
MessageSid
,
MessageStatus
,
ErrorCode

Graph Relationships

依赖关系

DEPENDS_ON

DEPENDS_ON

  • twilio
    SDK (Node 4.23.0 / Python 9.4.1)
  • Public HTTPS ingress (NGINX/ALB/API Gateway)
  • Persistent store for idempotency and message state (Postgres/DynamoDB)
  • Queue for async processing (SQS/Kafka/Redis Streams) recommended
  • twilio
    SDK(Node 4.23.0 / Python 9.4.1)
  • 公共HTTPS入口(NGINX/ALB/API网关)
  • 幂等性和消息状态持久化存储(Postgres/DynamoDB)
  • 推荐用于异步处理的队列(SQS/Kafka/Redis Streams)

COMPOSES

COMPOSES

  • twilio-voice
    (IVR can trigger SMS follow-ups; missed-call → SMS)
  • twilio-verify
    (OTP via SMS; share webhook infra and signature validation patterns)
  • sendgrid-email
    (fallback channel on undelivered; unified notification service)
  • observability
    (metrics/logging/tracing for webhook latency and delivery rates)
  • twilio-voice
    (IVR可触发SMS跟进;未接来电→SMS)
  • twilio-verify
    (SMS发送OTP;共享Webhook基础设施和签名验证模式)
  • sendgrid-email
    (未送达时的备用渠道;统一通知服务)
  • observability
    (Webhook延迟和送达率的指标/日志/追踪)

SIMILAR_TO

SIMILAR_TO

  • aws-sns-sms
    (SMS sending + delivery receipts, different semantics)
  • messagebird-sms
    /
    vonage-sms
    (carrier routing + webhook patterns)
  • firebase-cloud-messaging
    (delivery callbacks conceptually similar, different channel)
  • aws-sns-sms
    (SMS发送+送达回执,语义不同)
  • messagebird-sms
    /
    vonage-sms
    (运营商路由+Webhook模式)
  • firebase-cloud-messaging
    (送达回调概念类似,渠道不同)