Twilio × AWS サーバレス構成によるリマインド通知基盤の実践:SMS・音声通話の既読/応答判定と運用設計ガイド

Twilio × AWS サーバレス構成によるリマインド通知基盤の実践:SMS・音声通話の既読/応答判定と運用設計ガイド

本記事では、Twilio と AWSサーバレスアーキテクチャを活用し、SMS および音声通話を用いたリマインド通知における「既読」 「応答」状況の自動判定・記録方法について解説します。実際の業務ユースケースを踏まえ、未読/未応答ユーザーへの自動再送や KPI 管理を実現する設計と、運用上の注意点・ベストプラクティスをまとめています。

概要

ユーザーへのリマインド通知(リマインダー送信)は、日常業務やサービス運営で非常にニーズが高い機能です。しかし、「相手が本当にメッセージを見たか」 「電話に出たかどうか」を自動で検知・再送制御したい場合、実現方法は通信手段によって大きく異なります。

本記事では、Twilio を使ったリマインド通知の実装において、「SMS」 「音声通話(電話)」それぞれで開封・応答状況をどこまで判定できるのか、またその際のベストプラクティスや API 活用例について、現場のユースケースに即して解説します。

対象読者

  • 業務システムや Web サービスに「リマインド通知」を組み込みたいエンジニア
  • 顧客への重要連絡(支払い催促、予約確認、緊急通知等)を確実に届けたいプロダクト担当者
  • Twilio API(SMS / Voice)の運用・実装経験があり、通知の「未読・未応答」検出ロジックに悩んでいる方
  • 法人や自治体など、通知の開封/応答率を KPI 管理したい方

想定ユースケース

  • リマインダー(例:医療/美容予約・請求・納期通知)を送信し、「未読者のみ再送」したい
  • SMS 経由で「開封確認」を自動化したいが、LINE 等の既読機能が使えない
  • 自動音声通話(IVR)で「誰が出たか・出なかったか」に応じた再発信処理をしたい
  • セキュリティ通知(例:ワンタイムパスワード)などで「未着・未読」を検知したい

SMS の「既読・未読」判定はできるのか?

結論:Twilio × AWSサーバレスで「返信=既読」を実現できる

SMS には「既読確認」や「開封通知」のAPIはありません。そのため「既読/未読の自動判定」や「未読者だけ再送」 といった業務的な制御はアプリケーション側で実現する必要があります。 本記事ではその方法として、Twilio Webhook の着信を AWS のサーバレス(API Gateway+Lambda+DynamoDB)で受け、メッセージの既読/未読判定や再送判定をサーバ管理なしで自動化・永続化する実践例を紹介します。

この構成なら、インフラ管理不要で低コストかつスケーラブルに既読データを蓄積できるため、業務で使いやすい「未読者への自動再送」や「開封率の集計」などにも即応できます。

ここでは Twilio Webhook+API Gateway+Lambda+DynamoDB の最小構成で、リマインド SMS の既読判定&記録を検証する方法を紹介します。

1. システム全体の流れ

[ユーザー]
  ↑ SMS返信
[Twilio]
  ──(Webhook/HTTP POST)→ [API Gateway] → [Lambda] → [DynamoDB]
  • Twilio:SMS の返信を Webhook(HTTP POST)で API Gateway へ転送
  • API Gateway:外部からの POST リクエストを Lambda に中継
  • Lambda:Webhook の内容をパースし、「既読」かどうか判定
  • DynamoDB:既読(返信済み)・未読(返信なし)を記録

2. 必要な事前準備

  • TwilioのSMS送受信が可能な電話番号(管理画面で取得・確認)

  • AWSアカウント

    • API Gateway(REST API / HTTP API どちらでもOK)
    • Lambda関数(Node.js / Python 等)
    • DynamoDBテーブル(スケーラビリティ・運用コスト・サーバレス性を重視し採用。RDB 等他の DB でも構いませんが、検証・PoC には最小構成の DynamoDB が最適です)

3. 構築&検証手順

3.1 DynamoDB テーブル作成

AWS マネジメントコンソールから DynamoDB を開き、テーブルを作成してください。

  • テーブル名:RemindStatus
  • パーティションキー:phoneNumber(文字列型。例:+819012345678 など E.164 形式推奨)

※テーブル作成時は「ソートキー(range key)」は不要です。

3.2 Lambda関数を用意

Node.js 22.x での例です。まず、ローカル環境で node 環境を作成します。

# プロジェクト用ディレクトリを作成
mkdir reminder-webhook
cd reminder-webhook

# npm 初期化
npm init -y

# AWS SDK のインストール
npm install aws-sdk

次に、 index.js を作成します。

const AWS = require('aws-sdk');
const dynamo = new AWS.DynamoDB.DocumentClient();
const qs = require('querystring');

exports.handler = async (event) => {
    // Twilioからは x-www-form-urlencoded 形式で届く
    const body = qs.parse(event.body);
    const from = body.From;
    const msg = body.Body;

    // 「はい」と返信されたら既読
    let statusUpdate;
    if (msg && msg.trim().toLowerCase() === 'はい') {
        statusUpdate = {
            UpdateExpression: 'SET readAt = :now, latestReply = :msg, #st = :s',
            ExpressionAttributeNames: {
                '#st': 'status'
            },
            ExpressionAttributeValues: {
                ':now': new Date().toISOString(),
                ':msg': msg,
                ':s': 'read'
            }
        };
    } else {
        statusUpdate = {
            UpdateExpression: 'SET latestReply = :msg',
            ExpressionAttributeValues: { ':msg': msg }
        };
    }

    await dynamo.update({
        TableName: 'RemindStatus',
        Key: { phoneNumber: from },
        ...statusUpdate
    }).promise();

    // TwilioへXMLで空レスポンス
    return {
        statusCode: 200,
        headers: { "Content-Type": "text/xml" },
        body: "<Response></Response>"
    };
};

zip ファイルにまとめ、 Lambda コンソールにアップロードできる形にします。

zip -r function.zip .

Lambda関数の作成画面で「.zip ファイルをアップロード」オプションを選択し、function.zip をアップロードしてください。

次に、Lambda 関数の環境変数で DynamoDB 権限を付与します。Lambda関数の「設定 > アクセス権限 > 実行ロール」から、関連付けられているIAMロールを開きます。以下のようなカスタムポリシーをアタッチします。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:UpdateItem"
      ],
      "Resource": "arn:aws:dynamodb:ap-northeast-1:YOUR_ACCOUNT_ID:table/RemindStatus"
    }
  ]
}

IAM ロール

補足: Lambda コンソールを用いたテスト方法について

詳細はこちら

Lambda 関数が正しく動作するかどうかは、AWS コンソールの「テスト」タブを使って、Twilio から届く Webhook イベントを模擬して検証できます。

  1. Lambda 管理画面で関数を選択し、「テスト」タブを開く
  2. 新しいテストイベントを作成
    • 「テスト」タブで「テストイベントを作成」ボタンをクリック
    • 「イベント名」を任意で入力(例: TwilioReply
  3. テスト用イベント JSON を下記のように入力
    Twilio Webhook は x-www-form-urlencoded 形式ですが、API Gateway 経由では event.body に文字列として格納されます。そのため、下記のような JSON で body を模擬します:
    {
      "body": "From=%2B819012345678&Body=%E3%81%AF%E3%81%84"
    }
    
    • From=%2B819012345678:送信元番号(+819012345678 の URL エンコード)
    • Body=%E3%81%AF%E3%81%84:本文( 「はい」の URL エンコード)(参考:「はい」は UTF-8 で %E3%81%AF%E3%81%84 となります)
  4. テストイベントを保存し、「テスト」ボタンを実行
    • 正常に完了すれば、DynamoDB テーブル「RemindStatus」 で、該当 phoneNumberreadAtlatestReplystatusが更新されていることを確認できます。
    • DynamoDBの管理画面から、該当レコードを検索し、値が更新されているかチェックしてください。

3.3 API Gateway を設定

  • REST API(またはHTTP API)で POST エンドポイントを作成
  • Lambda 統合で上記関数を割り当て
  • デプロイして URL を発行(例: https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/default/reminder-webhook

補足: curl を用いたテスト方法について

詳細はこちら

「はい」と返信した場合のサンプルです。

curl -X POST \
  https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/webhook \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "From=%2B819012345678&Body=%E3%81%AF%E3%81%84"
  • 正常な場合
    statusCode: 200<Response></Response> が返ります
  • DynamoDB
    テーブル「RemindStatus」に該当の phoneNumber レコードが新規追加・更新されているか確認します
    テーブルの項目確認

3.4 Twilio 管理画面の Webhook 設定

電話番号設定画面で 「A MESSAGE COMES IN」 の Webhook URL 欄に、上記 API Gateway のエンドポイントを貼り付けます。

Webhook 設定

下記の手順で動作を検証します。

  1. サービス側から対象ユーザーにリマインド SMS を送信(本文に「このSMSを読んだら『はい』と返信してください」等を記載)
    サンプルコード
    const twilio = require('twilio');
    require('dotenv').config();
    
    const accountSid = process.env.TWILIO_ACCOUNT_SID;
    const authToken = process.env.TWILIO_AUTH_TOKEN;
    const messagingServiceSid = process.env.MESSAGING_SERVICE_SID;
    
    const client = twilio(accountSid, authToken);
    
    // 送信先電話番号(E.164形式、例:+819012345678)
    const to = '+819012345678';
    
    // 送信する本文
    const body = 'このSMSを読んだら「はい」と返信してください。';
    
    client.messages
        .create({
            to,
            body,
            messagingServiceSid
        })
        .then(message => {
            console.log(`Message SID: ${message.sid}`);
            console.log('送信完了');
        })
        .catch(error => {
            console.error('送信失敗:', error);
        });
    
  2. ユーザーが返信
  3. Twilio → API Gateway → Lambda → DynamoDB で既読記録
  4. DynamoDB コンソールで phoneNumber ごとに readAtstatus が更新されているか確認

音声通話(Voice)の「応答状況」はどこまで判定できるか?

結論:Twilio Voice API なら応答状況を API 経由で詳細に取得できる

音声通話(自動発信・IVR)では、Twilio Voice API が 「誰が電話に出たか」 「留守電だったか」 「応答しなかったか」などの発信先情報をプログラムで取得可能です。これにより、「電話に出ていない相手だけ再発信」 「人間以外(留守電)への通話は除外」など、より精度の高いリマインド運用が期待できます。

1. 判定できる項目例

Twilio の「Call Resource」 API で以下の情報が取得できます。

項目 意味 運用ポイント例
status completed / no-answer / busy等 「no-answer」で再送判定
answeredBy human / machine_start / unknown等 「human」のみ応答済とみなす
duration 通話時間(秒) 短すぎる場合は再送検討

2. サンプルコード(Node.js)

発信後に記録される「Call SID」をパラメータとして発信した結果の情報を取得できます。

const twilio = require('twilio');
require('dotenv').config();

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

// Call SID(発信後、callback等で記録しておく)
const callSid = 'CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';

client.calls(callSid)
  .fetch()
  .then(call => {
    console.log(`Status: ${call.status}`);
    console.log(`Answered by: ${call.answeredBy}`);
    console.log(`Duration: ${call.duration}s`);
    // 必要なロジック(例: answeredBy !== 'human' の場合に再発信など)
  })
  .catch(error => {
    console.error('取得失敗:', error);
  });

3. 運用のヒント

  • 通話ごとに応答状況を記録し、「未応答」や「留守電」だけ再発信するロジックを構築可能
  • DynamoDB や RDS に「通話結果」もあわせて記録すれば、SMS と同じく開封・応答状況を一元管理できる
  • 留守電ガイダンス対策や、短時間だけつながった場合の運用も柔軟にカスタマイズ可能

まとめ:Twilio × AWS でリマインド通知運用を最大化しよう

  • SMS は「返信=既読」だけ自動判定可能
    → Twilio Webhook+API Gateway+Lambda+DynamoDB のサーバレス最小構成が便利

  • 音声通話は「応答状況」まで API で取得可能
    → Voice API で「未応答」 「留守電」判定を取り込み、柔軟な再送や通知精度アップを実現

  • どちらも運用コストを最小化しつつ、業務要件に応じた「本当に見た/聞いた」人だけに自動再送ができる
    → KPI 管理、開封率・応答率向上にも寄与

運用・発展例

  • 未読者・未応答者一覧の自動出力や、通知履歴のダッシュボード化も DynamoDB+Lambda で実現可能
  • Twilio Functions や SaaS 連携を併用すれば、さらにノーコード寄りの小規模構成も実現できる
  • セキュリティ要件(IP 制限、署名検証など)にも本番環境では十分に配慮を

参考・関連ドキュメント

おわりに

Twilio と AWS のサーバレス構成を組み合わせることで、「通知を送っただけ」から 「誰に届き、誰が読んだ/応答したかを可視化・自動集計できる通知基盤」 が、最小限の構成とコストで実現できます。自社サービスの通知業務に「本当に見た人」だけへの再送や、KPI 管理を導入したい方に特におすすめです。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.