mcpField
T
twilio-api

twilio-api

github.com/tdimino开发4183 次安装1 周前更新
代码仓库

关于 twilio-api

twilio-api 是一项面向使用 Twilio 通信 API 构建应用的开发者参考技能。内容涵盖消息、语音、电话号码、Webhook,以及验证、重试和多服务商处理等生产实践。

主要功能

  1. 01SMS/MMS 和语音 API 指南
  2. 02电话号码管理与 E.164 格式验证
  3. 03使用 TwiML 处理 Webhook 并校验签名
  4. 04重试、速率限制和批量发送示例
  5. 05Twilio 与 Telnyx 的 Webhook 处理模式

适用场景

  1. 01构建双向 SMS 功能和通知
  2. 02处理入站消息与投递 Webhook
  3. 03开发语音 IVR 或电话号码工作流
  4. 04为生产环境部署消息集成

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/tdimino/claude-code-minoan --skill twilio-api

安装后在 Claude Code 中输入/twilio-api确认已加载。

附带文件README.mdreferences/index.md

详细介绍

Twilio API - 综合通信平台

何时使用此技能

在使用 Twilio 的通信 API 执行以下任务时,请使用此技能:

  • SMS/MMS 消息 - 以编程方式发送和接收文本消息
  • 语音通信 - 使用 TwiML 构建语音通话应用
  • 电话号码管理 - 搜索、购买和配置电话号码
  • Webhook 集成 - 使用 TwiML 响应处理实时事件和送达通知
  • 双向 SMS 对话 - 构建交互式 SMS 体验
  • 批量发送 SMS - 向多个收件人发送消息并进行速率限制
  • 消息调度 - 安排未来发送的消息
  • 生产部署 - 部署带有错误处理和监控的消息功能
  • A2P 10DLC 注册 - 注册品牌和营销活动,以满足美国 A2P 消息合规要求
  • 与服务提供商无关的架构 - 构建支持多个 SMS 服务提供商(Twilio + Telnyx)的系统

此技能适用于在应用中构建通信功能、设置 SMS 通知系统、创建语音 IVR 系统或集成电话功能。

快速参考

1. 发送简单 SMS(Node.js SDK)
const twilio = require('twilio');

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

async function sendSMS(to, from, body) {
  const message = await client.messages.create({
    to: to,
    from: from,
    body: body
  });
  return message;
}

// Usage
await sendSMS('+14155552671', '+14155559999', 'Hello from Twilio!');
2. 通过 HTTP 发送 SMS(无需 SDK)
const https = require('https');

function sendSMS(to, from, body) {
  const accountSid = process.env.TWILIO_ACCOUNT_SID;
  const authToken = process.env.TWILIO_AUTH_TOKEN;

  const auth = Buffer.from(`${accountSid}:${authToken}`).toString('base64');

  const postData = new URLSearchParams({
    To: to,
    From: from,
    Body: body
  }).toString();

  const options = {
    hostname: 'api.twilio.com',
    port: 443,
    path: `/2010-04-01/Accounts/${accountSid}/Messages.json`,
    method: 'POST',
    headers: {
      'Authorization': `Basic ${auth}`,
      'Content-Type': 'application/x-www-form-urlencoded',
      'Content-Length': postData.length
    }
  };

  return new Promise((resolve, reject) => {
    const req = https.request(options, (res) => {
      let data = '';
      res.on('data', (chunk) => { data += chunk; });
      res.on('end', () => resolve(JSON.parse(data)));
    });

    req.on('error', reject);
    req.write(postData);
    req.end();
  });
}
3. 验证电话号码(E.164 格式)
function validateE164(phoneNumber) {
  const e164Regex = /^\+[1-9]\d{1,14}$/;

  if (!e164Regex.test(phoneNumber)) {
    return {
      valid: false,
      error: 'Phone number must be in E.164 format (e.g., +14155552671)'
    };
  }

  return { valid: true };
}

// Normalize US phone numbers to E.164
function formatToE164(number) {
  let digits = number.replace(/\D/g, '');
  if (!digits.startsWith('1')) {
    digits = '1' + digits;
  }
  return '+' + digits;
}
4. 处理收到的消息(使用 TwiML 的 Webhook)
const express = require('express');
app.use(express.urlencoded({ extended: false }));

app.post('/webhooks/twilio', (req, res) => {
  const from = req.body.From;
  const body = req.body.Body;
  const to = req.body.To;

  console.log(`Received: "${body}" from ${from}`);

  // Respond with TwiML
  const twiml = `<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Message>Thanks for your message!</Message>
</Response>`;

  res.set('Content-Type', 'text/xml');
  res.send(twiml);
});
5. 验证 Webhook 签名(HMAC-SHA1)
const crypto = require('crypto');

function verifyTwilioSignature(url, params, signature, authToken) {
  // Build data string from sorted params
  const data = Object.keys(params)
    .sort()
    .reduce((acc, key) => acc + key + params[key], url);

  // Generate HMAC-SHA1 signature
  const expectedSignature = crypto
    .createHmac('sha1', authToken)
    .update(Buffer.from(data, 'utf-8'))
    .digest('base64');

  return signature === expectedSignature;
}

// Usage in Express with body-parser
app.post('/webhooks/twilio', (req, res) => {
  const signature = req.headers['x-twilio-signature'];
  const url = `https://${req.headers.host}${req.url}`;

  if (!verifyTwilioSignature(url, req.body, signature, process.env.TWILIO_AUTH_TOKEN)) {
    return res.status(403).send('Forbidden');
  }

  // Process webhook...
  const twiml = '<Response></Response>';
  res.set('Content-Type', 'text/xml');
  res.send(twiml);
});
6. Twilio SDK 签名验证
const twilio = require('twilio');

app.post('/webhooks/twilio', (req, res) => {
  const signature = req.headers['x-twilio-signature'];
  const url = `https://${req.headers.host}${req.url}`;

  if (!twilio.validateRequest(
    process.env.TWILIO_AUTH_TOKEN,
    signature,
    url,
    req.body
  )) {
    return res.status(403).send('Forbidden');
  }

  // Process webhook...
  const twiml = new twilio.twiml.MessagingResponse();
  twiml.message('Thanks for your message!');

  res.set('Content-Type', 'text/xml');
  res.send(twiml.toString());
});
7. 发送消息并进行错误处理和重试
async function sendWithRetry(to, from, body, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await client.messages.create({ to, from, body });
    } catch (error) {
      if (error.status >= 500 && attempt < maxRetries) {
        // Server error - retry with exponential backoff
        const delayMs = Math.pow(2, attempt) * 1000;
        console.log(`Retry ${attempt} in ${delayMs}ms...`);
        await new Promise(resolve => setTimeout(resolve, delayMs));
      } else {
        throw error;
      }
    }
  }
}
8. 使用速率限制进行批量发送
async function sendBulkSMS(recipients, from, body) {
  const delayMs = 100; // 10 messages/second
  const results = [];

  for (const recipient of recipients) {
    try {
      const result = await client.messages.create({ to: recipient, from, body });
      results.push({ success: true, to: recipient, sid: result.sid });
    } catch (error) {
      results.push({ success: false, to: recipient, error: error.message });
    }

    await new Promise(resolve => setTimeout(resolve, delayMs));
  }

  return results;
}
9. 与服务提供商无关的 Webhook 处理程序(Twilio + Telnyx)
// From Twilio-Aldea production codebase
function detectProvider(payload: any): 'twilio' | 'telnyx' {
  // Telnyx uses JSON with data.event_type
  if (payload.data && payload.data.event_type) {
    return 'telnyx';
  }

  // Twilio uses form-urlencoded with MessageSid
  if (payload.MessageSid || payload.From) {
    return 'twilio';
  }

  throw new Error('Unknown SMS provider');
}

// Unified webhook handler
app.post('/api/sms/webhook', async (req, res) => {
  const providerType = detectProvider(req.body);

  if (providerType === 'twilio') {
    // Validate Twilio signature
    // Return TwiML response
    const twiml = '<?xml version="1.0"?><Response></Response>';
    res.set('Content-Type', 'text/xml');
    res.send(twiml);
  } else {
    // Validate Telnyx Ed25519 signature
    // Return JSON response
    res.status(200).json({ status: 'ok' });
  }
});
10. 处理常见错误
function handleTwilioError(error) {
  if (!error.status) {
    return { type: 'NETWORK_ERROR', retriable: true };
  }

  switch (error.status) {
    case 400:
    case 422:
      // Validation error
      return {
        type: 'VALIDATION_ERROR',
        message: error.message,
        code: error.code,
        retriable: false
      };

    case 401:
      // Check Account SID and Auth Token
      return { type: 'AUTH_ERROR', retriable: false };

    case 429:
      // Rate limit
      return {
        type: 'RATE_LIMIT',
        retriable: true,
        retryAfter: 60
      };

    case 500:
    case 502:
    case 503:
      // Server error
      return { type: 'SERVER_ERROR', retriable: true };

    default:
      return { type: 'UNKNOWN_ERROR', retriable: false };
  }
}

关键概念

1. E.164 电话号码格式

国际电话号码格式:+[country code][number]

  • 美国示例:+14155552671
  • 英国示例:+442071234567
  • 始终包含 + 前缀
  • 最多 15 位数字(不包括 +)
2. 身份验证(Basic Auth)

Twilio 使用 HTTP Basic Authentication,Account SID 作为用户名,Auth Token 作为密码:

Authorization: Basic base64(ACCOUNT_SID:AUTH_TOKEN)
3. TwiML(Twilio 标记语言)

用于 Webhook 的 XML 响应格式:

<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Message>Your message text here</Message>
</Response>

常见的 TwiML 动词:

  • <Message> - 发送 SMS/MMS 回复
  • <Redirect> - 重定向到另一个 URL
  • <Dial> - 发起语音通话
  • <Say> - 文本转语音
  • <Play> - 播放音频文件
4. Webhook 事件

Twilio 会发送 application/x-www-form-urlencoded 格式的 POST 请求,其中包含:

  • MessageSid - 唯一的消息标识符
  • From - 发件人电话号码
  • To - 收件人电话号码
  • Body - 消息文本
  • MessageStatus - 消息状态(queued、sent、delivered、failed、undelivered)
  • NumMedia - 媒体附件数量(MMS)
5. 消息状态生命周期
  • queued - Twilio 已接受消息
  • sending - 正在发送给运营商
  • sent - 已发送给运营商
  • delivered - 已送达收件人(需要 StatusCallback)
  • undelivered - 发送失败
  • failed - 永久性失败
6. 签名验证(HMAC-SHA1)

Twilio 使用 HMAC-SHA1 对 Webhook 签名:

  1. 拼接 URL 和排序后的参数
  2. 使用 Auth Token 作为密钥生成 HMAC-SHA1
  3. 对结果进行 Base64 编码
  4. 与 X-Twilio-Signature 标头进行比较
7. A2P 10DLC 注册

对于美国消息服务,请注册:

  1. 品牌 - 您的企业实体
  2. 营销活动 - 使用场景(客户服务、营销、双重身份验证等)
  3. 电话号码 - 将号码与营销活动关联

时间线:审批需要 5-7 个工作日

8. 消息编码和分段
  • GSM-7:标准 ASCII 每段 160 个字符
  • UCS-2:表情符号/Unicode 每段 70 个字符
  • 长消息会拆分成多个段(最多 10 段)
  • 多段消息:GSM-7 每段 153 个字符,UCS-2 每段 67 个字符

Twilio-Aldea 的生产模式

模式 1:与服务提供商无关的 Webhook 架构
// Support both Twilio and Telnyx from single endpoint
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const rawBody = await readRawBody(req);

  // Auto-detect provider
  let payload: any;
  try {
    payload = JSON.parse(rawBody); // Telnyx
  } catch {
    payload = parseFormUrlEncoded(rawBody); // Twilio
  }

  const providerType = detectProvider(payload);
  const provider = getProviderByType(providerType);

  // Validate signature
  const isValid = provider.validateSignature(req, rawBody);
  if (!isValid) {
    return res.status(403).json({ error: 'Invalid signature' });
  }

  // Process message
  await processIncomingSMS(payload, provider);

  // Return provider-specific response
  if (providerType === 'twilio') {
    res.set('Content-Type', 'text/xml');
    res.send('<?xml version="1.0"?><Response></Response>');
  } else {
    res.status(200).json({ status: 'ok' });
  }
}
模式 2:为签名验证保留原始请求体
// Next.js API route config
export const config = {
  api: {
    bodyParser: false,  // Preserve raw body
  },
};

async function readRawBody(req: NextApiRequest): Promise<string> {
  return new Promise<string>((resolve, reject) => {
    let data = '';
    req.setEncoding('utf8');
    req.on('data', (chunk) => { data += chunk; });
    req.on('end', () => resolve(data));
    req.on('error', reject);
  });
}
模式 3:快速模式与计算模式
// Environment variable: SMS_FAST_MODE=true/false
const fastMode = process.env.SMS_FAST_MODE?.toLowerCase() !== 'false';

if (fastMode) {
  // Return immediate acknowledgment
  res.status(200).send(twiml);

  // Process async in background
  processIncomingSMS(payload).catch(console.error);
} else {
  // Wait for AI processing
  await processIncomingSMS(payload);
  res.status(200).send(twiml);
}
模式 4:TwiML 响应构建器
function buildTwiMLResponse(message?: string): string {
  if (!message) {
    return '<?xml version="1.0" encoding="UTF-8"?><Response></Response>';
  }

  // Escape XML special characters
  const escaped = message
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&apos;');

  return `<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Message>${escaped}</Message>
</Response>`;
}
模式 5:使用数据库实现幂等性
// PostgreSQL with unique constraint on message_sid
async function processWebhookIdempotent(messageSid: string, client: any) {
  try {
    await client.query('BEGIN');

    await client.query(
      'INSERT INTO processed_webhooks (message_sid, processed_at) VALUES ($1, NOW())',
      [messageSid]
    );

    await handleMessage(messageSid, client);
    await client.query('COMMIT');
  } catch (error: any) {
    await client.query('ROLLBACK');

    if (error.code === '23505') { // Duplicate key
      console.log('Message already processed');
      return;
    }

    throw error;
  }
}
模式 6:超时保护
function withTimeout<T>(
  promise: Promise<T>,
  timeoutMs: number = 25000
): Promise<T> {
  return Promise.race([
    promise,
    new Promise<T>((_, reject) =>
      setTimeout(() => reject(new Error('Timeout')), timeoutMs)
    ),
  ]);
}

// Usage
const result = await withTimeout(
  processIncomingSMS(payload),
  25000
);

API 基础知识

基础 URL
https://api.twilio.com/2010-04-01
身份验证
Authorization: Basic base64(ACCOUNT_SID:AUTH_TOKEN)
环境变量
# .env file
TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TWILIO_AUTH_TOKEN=your_auth_token_here
TWILIO_PHONE_NUMBER=+18005551234
速率限制
  • SMS 消息:每秒 200 条(企业版)
  • MMS 消息:根据品牌信任评分(10DLC)动态限制每个号码的发送量
  • 语音:默认支持 100 路并发通话
  • API 请求:默认每小时 10,000 次
常见响应结构
{
  "sid": "SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "date_created": "Wed, 18 Aug 2021 20:01:14 +0000",
  "date_updated": "Wed, 18 Aug 2021 20:01:14 +0000",
  "date_sent": null,
  "account_sid": "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "to": "+14155552671",
  "from": "+14155559999",
  "body": "Hello from Twilio!",
  "status": "queued",
  "num_segments": "1",
  "num_media": "0",
  "direction": "outbound-api",
  "price": null,
  "price_unit": "USD",
  "uri": "/2010-04-01/Accounts/ACxxx/Messages/SMxxx.json"
}

快速入门清单

  • 在 https://www.twilio.com/try-twilio 注册 Twilio 账户
  • 从控制台获取 Account SID 和 Auth Token
  • 设置环境变量
  • 购买一个电话号码用于测试
  • 发送第一条测试 SMS(快速参考 #1)
  • 验证电话号码(快速参考 #3)
  • 设置 webhook 端点(本地开发可使用 ngrok)
  • 使用 TwiML 实现 webhook 处理程序(快速参考 #4)
  • 添加 webhook 签名验证(快速参考 #5 或 #6)
  • 测试双向消息
  • 添加带重试逻辑的错误处理(快速参考 #7)
  • (仅限美国)如果要向美国号码发送消息,请注册 A2P 10DLC

使用此技能

初学者

从这里开始:

  1. 使用 快速参考 #1(发送简单 SMS)
  2. 设置环境变量
  3. 使用 快速参考 #3(验证电话号码)
  4. 测试向自己的电话号码发送消息
  5. 设置 快速参考 #4(使用 TwiML 处理传入消息)
  6. 使用 ngrok 测试双向消息

需要学习的关键概念:

  • E.164 电话号码格式
  • 基本身份验证(Account SID + Auth Token)
  • 用于 webhook 的 TwiML XML 响应
  • 消息状态生命周期

初学者常见错误:

  • 忘记在电话号码前添加 + 前缀
  • 未使用 E.164 格式
  • 将凭据硬编码,而不是使用环境变量
  • 未从 webhook 端点返回 TwiML
  • 未验证 webhook 签名
中级用户

重点领域:

  1. 实现 快速参考 #5 或 #6(签名验证)
  2. 使用 快速参考 #7(带重试的错误处理)
  3. 使用状态机构建对话流程
  4. 实现幂等性(生产模式 #5)
  5. 处理用于发送送达通知的 StatusCallback webhook

需要掌握的关键概念:

  • HMAC-SHA1 签名验证
  • TwiML 高级功能
  • 消息分段和成本优化
  • 错误处理模式
  • 批量发送的速率限制
高级用户

高级模式:

  1. 构建与提供商无关的处理程序(生产模式 #1)
  2. 实现超时保护(生产模式 #6)
  3. 设计多提供商架构
  4. 使用快速模式与计算模式进行优化(生产模式 #3)
  5. 使用 Voice API 构建 IVR 系统
  6. 设置全面的监控和告警

关键主题:

  • 与提供商无关的 webhook 架构
  • 基于数据库的幂等性
  • 结构化日志记录和监控
  • A2P 10DLC 合规性
  • 生产环境部署模式

常见错误代码

身份验证错误
  • 20003 - 身份验证失败(检查 Account SID 和 Auth Token)
  • 20005 - 账户未激活
验证错误
  • 21211 - “To”电话号码无效
  • 21212 - “From”电话号码无效
  • 21408 - 未启用向此号码发送消息的权限
  • 21610 - 尝试向已退订的收件人发送消息
速率限制错误
  • 20429 - 请求过多(已触发速率限制)
消息错误
  • 30001 - 队列溢出(系统过载)
  • 30003 - 目标无法访问
  • 30004 - 消息已拦截
  • 30005 - 未知目标
  • 30006 - 固定电话或运营商无法访问
  • 30007 - 消息已过滤(垃圾邮件)
  • 30008 - 未知错误

最佳实践

1. 始终验证 Webhook 签名
// Use Twilio SDK for built-in validation
const twilio = require('twilio');

if (!twilio.validateRequest(authToken, signature, url, params)) {
  return res.status(403).send('Forbidden');
}
2. 立即返回 TwiML
// Don't do expensive processing before responding
app.post('/webhook', async (req, res) => {
  // Return TwiML immediately
  res.set('Content-Type', 'text/xml');
  res.send('<Response></Response>');

  // Process async
  processMessage(req.body).catch(console.error);
});
3. 使用 StatusCallback 跟踪送达情况
await client.messages.create({
  to: '+14155552671',
  from: '+14155559999',
  body: 'Hello!',
  statusCallback: 'https://yourdomain.com/status'
});
4. 处理消息分段
// Keep messages under 160 characters for GSM-7
function optimizeForGSM7(text) {
  return text
    .replace(/[""]/g, '"')
    .replace(/['']/g, "'")
    .replace(/[—–]/g, '-')
    .replace(/…/g, '...');
}
5. 实现指数退避
async function sendWithBackoff(to, from, body, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await client.messages.create({ to, from, body });
    } catch (error) {
      if (attempt < maxRetries && error.status >= 500) {
        await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 1000));
      } else {
        throw error;
      }
    }
  }
}

TLS 要求

Twilio 将对 REST API 强制执行 TLS 1.3 和 TLS 1.2 密码套件限制(于 2026 年 3 月公布,截止日期为 2026 年 6 月)。请确认你的 HTTP 客户端库支持 TLS 1.3 或符合要求的 TLS 1.2 密码套件。大多数现代版本的 Node.js(18+)和 Python(3.10+)默认都满足此要求。

消息调度

消息调度现已正式推出。使用 SendAt 和 MessagingServiceSid 参数安排未来发送的消息:

await client.messages.create({
  to: '+14155552671',
  messagingServiceSid: 'MGxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
  body: 'Scheduled message',
  sendAt: new Date('2026-06-01T14:30:00Z'),
  scheduleType: 'fixed',
});

消息最多可提前 7 天安排。需要使用 Messaging Service(不能使用原始电话号码)。

资源

版本说明

此技能包含:

  • Twilio 官方 API 模式和最佳实践
  • 来自 Twilio-Aldea SMS 平台的生产代码示例
  • 与提供商无关的 webhook 架构
  • TwiML 响应模式
  • 完整的签名验证示例
  • TypeScript 和 JavaScript 示例
  • TLS 1.3 要求更新(截止日期为 2026 年 6 月)
  • 消息调度(GA)和动态 MMS 速率限制

评论

支持 Markdown