cloudflarehonolinevonage個人開発
予定を忘れて実害が出る問題は3段構造になっている。
LINEに送るだけなら登録のハードルは限りなく低い。通知じゃなくて電話にすれば無視できない。この2つの組み合わせで全部解ける。リマインくん(日本で600万人が使ってるLINEリマインダーBot)と同じ操作感で、通知手段だけが電話になる。
LINE ──webhook──> Cloudflare Workers (Hono)
│
D1 (SQLite)
│
cron (毎分) → Vonage Voice API → 電話
| レイヤー | 技術 |
|---|---|
| ランタイム | Cloudflare Workers(エッジで動くサーバーレス実行環境) |
| フレームワーク | Hono v4(軽量Webフレームワーク) |
| DB | D1(Workers内蔵のSQLiteデータベース) |
| 音声通話 | Vonage Voice API(NCCO(通話制御スクリプト)+ 日本語TTS(音声読み上げ)) |
| 決済 | Stripe Checkout Sessions |
| 日本語解析 | chrono-node/ja(日本語の日時をプログラムで解析するライブラリ)+ 自前の前処理 |
全部サーバーレス。月の固定費ゼロ、従量課金だけ。
| サービス | 日本携帯/分 | 課金単位 | 30秒コスト |
|---|---|---|---|
| Vonage | $0.154 | 1秒 | ¥12.30 |
| Twilio | $0.185 | 実質60秒 | ¥29.60 |
| Telnyx | 非公開 | 60秒 | 算出不可 |
課金単位が決定打。 リマインダー通話は30秒で終わるのに、Twilioは3秒でも1分分請求される(実測確認)。Vonageは秒単位課金なので半額。Telnyxは60秒課金が明記されてるので論外。
050番号(日本の電話番号)は犯罪収益移転防止法のKYC(本人確認)で法人/個人事業主のみ取得可能。個人開発者はアウト。海外番号で運用し、認証完了時にvCard(連絡先ファイル)で電話帳追加してもらう設計にした。
1. chrono-node/jaが「5分後」を理解しない
日本語日時解析ライブラリが相対時間(「N分後」「N時間後」)に非対応。自前で正規表現パーサーを書いて、chrono-nodeより先に処理する方式で解決。
2. タイムゾーンがnull(Workers環境の罠)
chrono-node/jaがタイムゾーン情報を返さない。ローカル(日本時間環境)では問題ないが、Cloudflare WorkersはUTC(世界標準時)環境。「明日9時」が9:00 UTCつまり日本時間18時になってしまう。明示的にJST(+9時間)を割り当てて回避。
3. 漢数字の前処理
chrono-node/jaは漢数字に非対応。「二十二時」→「22時」の前処理を自作。十二→12、二十二→22を正しく変換。
4. 「内容」の抽出が意外と難しい
「3日後に母に電話する」→「母に電話する」(内容の一部)。「明日9時に薬を飲むって教えて」→「薬を飲む」(Botへの指示を除去)。否定先読みという正規表現テクニックで切り分け。テストケース47個。
5. JWT(認証トークン)をWeb Crypto APIで自前実装
Vonageの認証にはRS256署名が必要。Workers環境ではNode.js用のJWTライブラリが動かないため、ブラウザ標準のWeb Crypto APIで直接署名処理を実装。外部ライブラリゼロ。秘密鍵の破損で4時間通話停止するインシデントも経験。
「歯医者」 → 「何時に電話する?」→ 「明日9時」 → 登録完了
「明日」 → 「何時に電話する?」→ 「9時」 → 「なんて電話する?」→ 「歯医者」 → 登録完了
途中まででもOK。3つの状態をUnion Type(合体型)で表現、30分で期限切れ。D1のカラム1つにJSON保存。Redis(高速キャッシュ)は使わず、SQLiteだけで完結。
1コール約¥13(Vonage Pricing APIの実測値。USD建てなので為替リスクあり)。生涯3回無料+回数パック(10回¥300 / 20回¥540 / 50回¥1,200)。新規ユーザー1人の無料コストは¥52(認証¥13+3回¥39)。月5,000円予算で96人まで対応可能。
約2週間・189コミット。全機能をTDD(テスト駆動開発:テストを先に書いてからコードを書く手法)で実装。テスト合計5,000行超。Stripe課金は6回のテスト→実装サイクルで1.5時間で完了。
セキュリティ: LINE署名のtiming-safe(時間差攻撃対策)比較、Stripe Webhookの冪等性(同じ処理が2回来ても壊れない仕組み)、Vonage Webhookのトークン認証、電話番号の存在漏洩防止、管理画面のレート制限。認証コードのハッシュ化やJWT単体テストはYAGNI(今は不要)と判断。
反省: 秘密鍵破損で4時間通話停止(.dev.varsを正本にしてたおかげで5分復旧)。DBマイグレーション忘れでデプロイ後に500エラー(ヘルスチェック運用に変更)。
技術的な山場: chrono-node/jaの罠(相対時間非対応・タイムゾーンnull)と、Voice API選定(Vonageの秒単位課金が決定打)の2つだった。