【ITニュース解説】Mastering Email Rate Limits — A Deep Dive into Resend API and Cloud Run Debugging
2025年09月07日に「Dev.to」が公開したITニュース「Mastering Email Rate Limits — A Deep Dive into Resend API and Cloud Run Debugging」について初心者にもわかりやすいように丁寧に解説しています。
ITニュース概要
メールAPIの利用制限で、学習プラットフォームのメール送信が頻繁に失敗。Cloud Runのログ解析で原因を特定した。解決策は、バッチ処理で複数のメールを一度に送る、処理順序を見直す、失敗時に再試行する仕組みの導入だ。結果、メール送信は安定し、失敗はゼロになった。
ITニュース解説
システム開発において、メール送信機能はユーザーとのコミュニケーションに不可欠な要素です。しかし、この一見シンプルな機能も、本番環境で大量のメールを扱うようになると、予期せぬ問題に直面することがあります。特に、外部のメール送信サービスを利用する場合、「レート制限」という壁にぶつかることは少なくありません。これは、サービスプロバイダーがサーバーへの過剰な負荷を防ぐため、一定時間内に受け付けるリクエストの回数を制限する仕組みです。
私たちのAI学習プラットフォーム「DailyMastery」でも、まさにこの問題に直面しました。ユーザーに毎日パーソナライズされたレッスンを送信するシステムは、開発段階では問題なく動作していました。しかし、実際に多くのユーザーが利用する本番環境にデプロイすると、原因不明のメール送信失敗が頻発するようになったのです。システムはGoogle Cloud Run上で稼働し、メール送信には「Resend」というAPIサービスを利用していました。
初期のシステム設計では、レッスンをスケジュールする処理が動くたびに、ユーザー一人ひとりのレッスンメールを個別にResendのAPIに送信していました。この設計には、メール送信の頻度を制御する仕組み(レート制限保護)が考慮されていませんでした。根本的な原因は、Resendが設定していた「1秒間に2リクエストまで」という厳しいレート制限でした。複数のユーザーに対して、さらに一人あたり複数のレッスンを処理しようとすると、あっという間にこの制限を超過してしまい、大量のメール送信が失敗するという事態が発生したのです。
この問題を解決するためには、まず何が起こっているのかを正確に把握する必要がありました。最初のステップは、システムに詳細なログ出力機能を追加することでした。メール送信の開始、Resend APIへのリクエスト内容、そしてResendからの応答を、成功か失敗かにかかわらずすべて記録するようにしました。特に重要な発見は、ResendのAPIがレート制限を超えた場合でも、通常のエラーのように例外(プログラムの実行を中断させるエラー)を発生させず、単に結果の中にエラー情報を含んだオブジェクトを返すという挙動でした。このため、エラーを適切に検知できていなかったのです。
ログを収集するだけでなく、それを分析することも重要です。私たちはGoogle Cloud Runのログ分析機能を活用しました。gcloud logging readコマンドを使うことで、指定したサービス(今回はレッスンをスケジュールするサービス)のログをリアルタイムで監視したり、特定のエラーメッセージ(例:「rate_limit」や「429」(Too Many Requestsエラーコード))でフィルタリングして、問題発生時の状況を詳細に調査しました。これにより、メール送信失敗の具体的な原因がResendのレート制限であることが明確に裏付けられました。
問題を特定した後、私たちは多層的なアプローチで解決策を講じました。
第一に、前述したResendのAPIの特性を考慮し、メール送信結果の正確なエラー検出を実装しました。APIの応答を詳細にチェックし、エラーオブジェクトが存在する場合や、期待するデータが返されない場合には、それを失敗として適切に処理するようにしました。
第二に、「インテリジェントなバッチメール送信」機能を導入しました。Resendは最大100件のメールを一度に送信できるバッチAPIを提供しています。私たちはこの機能を活用し、送信対象のメールが100件を超える場合は、自動的に100件ごとの「チャンク」(まとまり)に分割して処理するようにしました。さらに、各チャンクの送信処理が終わるごとに、1秒間の遅延を挟むことで、次のバッチがすぐにレート制限に引っかかるのを防ぎました。これにより、大量のメールを効率的かつ安全に送信できるようになりました。
第三に、システムのアーキテクチャ全体を見直しました。以前は、レッスンの準備と同時に個別にメールを送信していましたが、新しいアプローチでは、「すべてのメールを準備するフェーズ」と「準備したすべてのメールをまとめてバッチで送信するフェーズ」を完全に分離しました。これにより、まず送信すべきメールをリストアップし、その後で最適化されたバッチ送信ロジック(前述の自動チャンクや遅延処理を含む)を適用することが可能になりました。また、メールが実際に成功裏に送信された数に基づいてのみ、レッスンを「送信済み」とマークするよう改善し、失敗した場合は再送などの対応が取りやすいようにしました。
第四に、管理者へのレポートなど、特に重要なメールについては、スマートなリトライ(再試行)ロジックを実装しました。通常のレッスンメールのバッチ送信が完了した後、少し時間を置いてから(例:3秒後)レポートメールを送信し、もしそのメールがレート制限によって失敗した場合は、さらに長い時間(例:10秒後)待ってから再試行するようにしました。これは「エクスポネンシャルバックオフ」と呼ばれる手法の一種で、失敗するたびに待機時間を長くすることで、サービスへの負荷を減らしつつ、最終的な成功確率を高めます。
Cloud Runでのデバッグでは、いくつかの重要なテクニックが役立ちました。ログ出力の際には、絵文字や構造化されたデータ(例えば、{ count: emails.length } のようにキーと値のペアで情報を出力する)を使用することで、ログがより分かりやすく、分析しやすくなりました。また、デプロイしたコードが本当に最新のものになっているかを確認するために、gcloud run services describe コマンドで現在のリビジョン(デプロイされたコードのバージョン)を確認する習慣も重要でした。さらに、gcloud beta logging tail を使ってリアルタイムでログをストリーミング監視することで、問題が起きている瞬間の挙動を即座に把握することができました。
これらの最適化を行った結果、以前は30%ものメールが失敗していた状況が劇的に改善し、メールの失敗率は0%になりました。ユーザーがレッスンを受け取れないという問題は解消され、手動での介入は一切不要になりました。システムの処理時間も効率化され、例えば1人のユーザーに3通のメールを送る処理は7秒程度で完了するようになりました。
この経験から、いくつかの重要なベストプラクティスを学びました。メールサービスを設計する際には、常に「成功したか、失敗したか、そしてなぜ失敗したか」を明確に返す、構造化された結果を返すようにするべきです。また、レート制限対策として、可能な限りバッチAPIを優先して利用し、大きなバッチは自動的に小さなチャンクに分割すること、そして各処理の間に知的な遅延を挟むことが非常に効果的です。送信状況を正確に追跡し、失敗した場合には適切にリトライする仕組みも不可欠です。
結論として、信頼性の高いメール配信システムを構築するためには、利用するメールプロバイダーの制限を深く理解し、それに対応する堅牢なアーキテクチャとデバッグ戦略を事前に計画することが不可欠です。レート制限は現実の課題であり、バッチAPIはゲームチェンジャーとなり得ます。そして、包括的なログ記録とフォールバック戦略(予備の対策)は、本番環境でシステムを安定稼働させる上で極めて重要です。システムは、ただ動くだけでなく、問題が発生した際にも信頼性高く動作するよう設計されている必要があります。