← 一覧に戻る

LINEに「明日9時に歯医者」って送ると電話がかかってくるBotを作った

2026年3月30日 09:00 更新 — Zenn記事ドラフト

cloudflarehonolinevonage個人開発

一言でいうと

LINEに予定を送ると電話でリマインドするBot。約2週間で個人開発。

なぜこれが必要なのか

予定を忘れて実害が出る問題は3段構造になっている。

  1. 予定を忘れて実害が出る — 歯医者すっぽかしてキャンセル料、ミーティング飛ばして信頼失墜
  2. リマインダーの登録自体を忘れる — カレンダーに入れるワンステップを忘れる
  3. テキスト通知はスルーできる — 他の通知に埋もれて「今すぐ動かなきゃ」にならない

LINEに送るだけなら登録のハードルは限りなく低い。通知じゃなくて電話にすれば無視できない。この2つの組み合わせで全部解ける。リマインくん(日本で600万人が使ってるLINEリマインダーBot)と同じ操作感で、通知手段だけが電話になる。

何をしたのか

技術構成

LINE ──webhook──> Cloudflare Workers (Hono)
                        │
                   D1 (SQLite)
                        │
                   cron (毎分) → Vonage Voice API → 電話
レイヤー技術
ランタイムCloudflare Workers(エッジで動くサーバーレス実行環境)
フレームワークHono v4(軽量Webフレームワーク)
DBD1(Workers内蔵のSQLiteデータベース)
音声通話Vonage Voice API(NCCO(通話制御スクリプト)+ 日本語TTS(音声読み上げ))
決済Stripe Checkout Sessions
日本語解析chrono-node/ja(日本語の日時をプログラムで解析するライブラリ)+ 自前の前処理

全部サーバーレス。月の固定費ゼロ、従量課金だけ。

Voice API選定 — なぜVonageか

サービス日本携帯/分課金単位30秒コスト
Vonage$0.1541秒¥12.30
Twilio$0.185実質60秒¥29.60
Telnyx非公開60秒算出不可

課金単位が決定打。 リマインダー通話は30秒で終わるのに、Twilioは3秒でも1分分請求される(実測確認)。Vonageは秒単位課金なので半額。Telnyxは60秒課金が明記されてるので論外。

050番号(日本の電話番号)は犯罪収益移転防止法のKYC(本人確認)で法人/個人事業主のみ取得可能。個人開発者はアウト。海外番号で運用し、認証完了時にvCard(連絡先ファイル)で電話帳追加してもらう設計にした。

ステップ / 詳細

ハマりどころ5選

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つだった。

📝 質問モード — テキストを選択してね
✓ 質問を送信しました