【ITニュース解説】How to Poll Transactional Email Delivery Status in Node.js: Cron Without Webhooks
2026年09月23日に「Dev.to」が公開したITニュース「How to Poll Transactional Email Delivery Status in Node.js: Cron Without Webhooks」について初心者にもわかりやすく解説しています。
ITニュース概要
Node.jsでWebhooksを使わず、Cronで確認メール等の配信状況をポーリングする手法を解説。パスワードリセット用トークン有効期限は配信状況とは切り離し、アプリ側で管理する。ポーリングは証拠収集であり、セキュリティポリシーとは別問題として扱う。
ITニュース解説
システム開発では、ユーザーへの重要な通知、例えばパスワードリセットや注文確認メールの配信状況を正確に把握することが不可欠だ。しかし、「メールが本当にユーザーに届いたのか」という確認は、見た目以上に複雑な課題をはらんでいる。
一般的なメール送信サービスはダッシュボードを通じてメールの送信ステータスを提供するが、「配信済み」という表示だけでは、そのメールがユーザーの意図したアクション(例えばパスワードの変更)に確実につながったのかまでは判断できない。もしメールが届かなかった場合、どのユーザーの、どの操作が原因で問題が発生したのかを迅速に突き止めるのは難しい。
そこでこの記事では、リアルタイムな通知を受け取る「Webhook」を使わず、定期的にサービスに問い合わせて情報を取得する「ポーリング」という手法で、トランザクションメール(取引やシステムからの自動通知メール)の配信ステータスを追跡する方法を提案している。Node.jsで書かれたプログラムをCronというスケジューラーで定期的に実行する「Cronワーカー」が、このポーリングを担当する。
Webhookは、メールが配信されたなどのイベントが発生した際に、メールサービス側から指定されたURLに情報を自動で送信(プッシュ)する仕組みだ。これはリアルタイム性に優れるが、受け取るサーバー側で通知を適切に処理する仕組みや、セキュリティ対策が必要となる。一方、ポーリングは、こちらから一定間隔でサービスに「今どうなっていますか?」と問い合わせて情報を取得(プル)する方法で、受け取り側の準備は比較的シンプルになる場合がある。
セキュリティの観点から、メールの配信状況を確認する行為は、あくまで「証拠を集める」ことだと強調されている。これは、ユーザーがパスワードを変更するといった「認証」の行為とは直接関係ない。特に、パスワードリセットメールに含まれる一時的なトークンは、メールが届いたかどうかに関わらず、システムが定めた時間(例えば15分)が経過したら必ず無効になるように設計すべきだ。このトークンの有効期限は、ポーリングの間隔やメールの配信状況とは独立した、セキュリティ上の重要なポリシーである。たとえメールが遅れて届き、後から配信完了が確認されたとしても、既に期限切れのトークンが再び有効になることは絶対にない。ポーリング自体も、例えば1分ごとに10分間まで、と監視期間に明確な制限を設けるべきだ。
Cronワーカーでの具体的なポーリング処理は、「境界付き重複ウィンドウ」という考え方を用いる。これは、ワーカーが実行されるたびに、過去に送信を試みたメールの中から、まだ最終的な配信状況を確認できていないもの、かつ、監視期間が終了していないものを特定して確認対象とするものだ。メール送信プロバイダーから「キューに登録された」「配信済み」「遅延」「バウンス(不達)」といった様々なイベントが通知されるが、これらを自社のシステムが理解できる「送信待ち」「送信成功」「送信失敗」といった独自の統一された状態に変換(正規化)して保存する。もしプロバイダーへの問い合わせが一時的に失敗した場合、そのメールのステータスは「未処理」のままにしておくことが重要だ。もし読み取り失敗を成功として記録してしまうと、システムが実際の状況を見誤る「サイレントなギャップ」が生まれてしまうからだ。
記事中にGo言語で示されたコードは、このポーリング処理の核となる部分「Reconcile(調整・和解)」の考え方を示している。Attemptというデータ構造で、どのメールを、いつ最後にチェックし、いつまで監視するかといった送信試行の情報を管理する。Eventというデータ構造で、プロバイダーからの配信状況に関するイベントを表現する。そして、Readerがプロバイダーからイベントを読み出す役割を、Storeがこれらの情報をデータベースに保存・更新する役割を担う。Reconcile関数は、Storeから未処理のAttemptを取得し、Readerで最新のイベントを読み込み、それをStoreに記録し、Attemptの最終チェック時刻を更新するという一連の処理を実行する。この基本的な考え方は、Go言語だけでなくNode.jsで実装する場合でも共通して適用できる。
システム運用においては、役割分担を明確にすることが重要だ。メールの件名や本文、多言語対応といったテンプレートの管理はアプリケーション開発チームが、トークンの有効期限や一度しか使えないといったセキュリティに関するチェックはセキュリティチームが、そして実際にメールを送信する外部サービスとの連携はトランスポートアダプターが担当するといった具合だ。これにより、各チームがそれぞれの専門分野に集中でき、システム変更時のロールバック(元の状態に戻す作業)もより安全かつ容易になる。個人情報はイベントログにできるだけ含めないようにし、メール送信に関わる法律(例えば米国のCAN-SPAM法など)を遵守することも大切だ。
このシステムを導入する際は、様々な状況を想定したテストが不可欠である。例えば、メールがキューに登録されてから実際に配信されるまで、遅延を経て最終的に不達となるケース、同じイベントが重複して通知されるケース、そして監視期限まで何のイベントも来ないケースなど、考えられるあらゆるシナリオで動作を確認する。特に、遅れて届いたメールの配信確認によって、すでに有効期限が切れたトークンが再び有効になることがない、という点を厳しく検証すべきだ。
システムの監視も重要だ。未処理のメール送信試行の数、プロバイダーからのイベント読み取り失敗回数、監視期限が切れてしまった送信試行、そして全体のポーリング処理の遅延などを継続的に計測し、異常があればアラートを出すように設定する。ただし、単一のメール配信失敗だけで即座にアラートを出すのではなく、継続的な問題や監視期限が目前に迫った状況でアラートを出すように調整する。新しいシステムを導入する際には、まず「シャドウモード」で運用し、ユーザーの状態に影響を与えずにイベントを読み取り、監査記録だけを作成することから始めるのが安全な方法である。
このポーリング手法は、リアルタイム性よりもシステムの堅牢性や管理のしやすさを重視する場合に適している。特にWebhookの導入が難しい、あるいはその運用コストを避けたい場合に有効な選択肢となるだろう。しかし、非常に高いリアルタイム性が求められる場合や、プロバイダー側のイベント量が膨大である場合には、Webhookのようなプッシュ型のシステムの方が適していることもある。いずれの方式を選ぶにしても、重要なのは、各システムが独立してセキュリティポリシー(例:トークンの有効期限)を管理し、メールの配信状況はあくまで補助的な情報として活用することだ。これにより、より信頼性の高いシステムを構築できる。