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. 03TwiMLによる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 は、アカウント SID をユーザー名、Auth Token をパスワードとして HTTP Basic 認証を使用します。

Authorization: Basic base64(ACCOUNT_SID:AUTH_TOKEN)
3. TwiML(Twilio Markup Language)

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 は、次の内容を含む 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. キャンペーン - ユースケース(カスタマーケア、マーケティング、2FA など)
  3. 電話番号 - 電話番号をキャンペーンに関連付けます

所要期間:承認まで 5~7 営業日

8. メッセージのエンコーディングと分割
  • GSM-7:標準 ASCII の場合、1 セグメントあたり 160 文字
  • UCS-2:絵文字/Unicode の場合、1 セグメントあたり 70 文字
  • 長いメッセージはセグメントに分割されます(最大 10)
  • マルチパート:GSM-7 は 1 セグメントあたり 153 文字、UCS-2 は 1 セグメントあたり 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 リクエスト:1 時間あたり 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 送信レート制限

コメント