ガイド · メールAPI
プロダクトチームはメールAPIをどう安全に実装すべきですか?
メールAPIは、フォームからプロバイダーを直接呼び出すのではなく、権限管理された非同期のワークフローとして実装します。呼び出し元を認証し、検証済みのFromドメインをテナントが所有していることを確認し、メッセージを検証してサイズを確認し、安定したアプリケーションのジョブIDを割り当て、キューには一度だけ登録し、ワーカーから送信します。受け付けられた時点でプロバイダーのメッセージIDを記録し、配信イベントを冪等に取り込み、恒久的なバウンスと苦情はサプレッションリストに追加します。上限付きの再試行は、重複のリスクを制御できている場合にのみ使います。認証情報はサーバー側に置き、ログに含めるメッセージのデータは最小限にし、APIによる受け付け、メールサーバーへの配信、受信トレイへの到達を区別してください。
プロバイダーを選ぶ前にAPIの境界を定義する
メールAPIは、プロバイダーの詳細をすべてプロダクトのコードに漏らすことなく、アプリケーションの意図を表現できるべきです。メッセージ、送信ドメイン、APIキー、イベント、サプレッションをリソースとして定義します。From、To、Reply-To、件名、テキスト、HTML、そして少数の許可リストに載ったヘッダーなど、呼び出し元がどのフィールドを制御できるかを決めておきましょう。プロバイダーの署名やルーティングと衝突しうる、呼び出し元が指定した転送用ヘッダーは拒否してください。送信は影響の大きい書き込み操作として扱います。レスポンスではアプリケーションのメッセージリソースとその現在の状態を示すべきで、メールボックスでの結果を暗示してはいけません。プロバイダーのアカウント、リージョン、設定セット、転送に関する識別子はアダプターの背後に隠します。この境界があることで、プロバイダーの移行が可能になり、認可、保持、不正利用対策を置く場所も安定します。
呼び出し元を認証し、すべての送信ドメインを認可する
APIキーは一方向ハッシュとしてのみ保存し、完全な秘密情報は一度だけ表示します。各キーには、所有するワークスペース、ステータス、作成日時、失効手段を持たせます。ある連携に送信だけ、あるいはイベントの読み取りだけを許可したい場合は、より狭いスコープを追加しましょう。認証は誰が認証情報を提示したかを確認するもので、認可はその主体が要求されたFromドメインやメッセージリソースを使ってよいかを判断するものです。クライアントが指定したドメインIDを信頼するのではなく、バッチ用エンドポイントも含めて送信のたびにドメインの所有権を確認してください。本番のトラフィックを有効にする前に、プロバイダーでの検証を必須にします。プロバイダーの認証情報やワークスペースのAPIキーを、ブラウザのJavaScript、クエリ文字列、アナリティクス、エラーメッセージに含めてはいけません。マルチテナントのAPIでは、メッセージ、イベント、サプレッション、インボックス、ドメインの識別子に対するオブジェクトレベルの認可が特に重要です。
ドメインを検証し、認証をアライメントさせる
送信ドメインには、データベース上のフラグ以上のものが必要です。プロバイダーの所有権確認を完了し、必要なDKIMレコードを公開してください。SPFはSMTPのMAIL FROMまたはHELOのIDに対して送信ホストを許可し、DKIMは署名ドメインをメッセージの暗号署名に結び付けます。DMARCは、成功したSPFまたはDKIMの識別子が、表示上のRFC 5322のFromドメインとアライメントしているかを評価し、ドメイン所有者が処理方法とレポートのポリシーを公開できるようにします。ドメインにすでにSPFがある場合は、必要なメカニズムを既存のレコードに統合してください。RFC 7208では、複数のSPFレコードが選択される原因となるレコードを公開してはならないと定められています。より厳格なDMARCポリシーに移行するのは、管理されたテストメッセージと集計レポートによって、正規の送信元がすべてアライメントしていることを確認してからにしましょう。認証はドメインの不正使用を減らしますが、受信トレイへの到達を保証するものではありません。
メッセージの構造を検証し、受け付ける入力を最小限にする
RFC 5322では、インターネットメッセージをヘッダーフィールドと任意の本文から成るものと定義しており、MIMEの仕様によって基本的なテキスト以外のコンテンツにも拡張されています。APIは通信フォーマットの細部の大半を隠しつつ、それらを強制することができます。受信者の配列を正規化し、受信者数とエンコード後の合計サイズに上限を設け、テキストまたはHTMLの本文を少なくとも1つ必須にし、構文が正しいことをメールボックスの存在の証明とみなさずにアドレスを検証します。ヘッダーになるフィールドからは、キャリッジリターンと改行文字を取り除いてください。Message-IDは自分で生成するか、プロバイダーに生成させます。メッセージの新しいバージョンには正当に新しい識別子が割り当てられることがあるため、Message-IDをアプリケーションのジョブIDとして使い回してはいけません。カスタムヘッダーはドキュメント化されたものだけを許可し、保護されたフィールドの重複は拒否します。変数の欠落が管理されたアプリケーションの状態として失敗するよう、テンプレートはプロバイダーへの送信前にレンダリングしましょう。
キューへの登録は一度だけにし、安定したアプリケーション識別子を使う
ユーザーのリクエストでは、トランザクションの中で永続的なメッセージジョブを1つ作成し、プロバイダーの呼び出しはワーカーが行うようにします。ジョブには安定した識別子を付け、契約がサポートしている場合は、リクエストのフィンガープリントや呼び出し元が指定した冪等キーを記録します。HTTPでは、POSTはデフォルトで冪等ではないと定義されており、操作が実質的に冪等であるとクライアントがわかっている場合や、元のリクエストが適用されなかったとわかっている場合を除き、自動再試行を行わないよう注意喚起しています。これはメールにとって重要です。プロバイダーがメッセージを受け付けた後、ワーカーがレスポンスを受け取る前にタイムアウトが発生することがあるからです。結果があいまいな失敗の場合は、新たに送信するのではなく、まず保存済みのジョブとプロバイダー側の状態を突き合わせてください。アプリケーションの状態とキューへの登録を一緒に進める必要がある場合はアウトボックスパターンを使い、冪等性の境界には一意性制約を設けましょう。
失敗の種類に応じて再試行を設計する
バリデーション、認可、スロットリング、プロバイダーによる拒否、一時的な転送エラー、受信者への配信失敗を区別します。不正な入力や認可されていないFromドメインは、再試行せずに失敗させるべきです。プロバイダーのレート制限や一時的なサービスエラーは、上限付きの指数バックオフ、ジッター、試行回数の上限、そしてワーカーのリクエスト期限より長いキューの可視性タイムアウトを設定して再試行できます。あいまいなネットワークタイムアウトには、無条件に新しいリクエストを送るのではなく、重複を考慮した突き合わせが必要です。SMTP自体は一時的な4xxと恒久的な5xxの応答を区別しますが、プロバイダーのAPIを使うアプリケーションは、そのプロバイダーのドキュメントに記載されたエラーの意味に従うべきです。再試行を使い切ったジョブは、確認可能なデッドレターの状態に移し、機密情報を除いた理由を保持します。受信者の恒久的なバウンスをAPIの障害のように再試行してはいけません。また、苦情を新たな送信試行につなげてはいけません。
受け付けを記録し、配信イベントを取り込む
プロバイダーメッセージIDは、受け付け直後に永続化し、アプリケーションメッセージIDにマッピングしてください。これにより、苦情レポートが受信者の詳細を伏せていても、プロバイダーイベントで正しいリソースを更新できます。たとえばAmazon SESは、送信成功と受信者のメールサーバーへの配信を区別し、配信、バウンス、苦情、拒否、配信遅延、レンダリング失敗、開封、クリックのイベントを公開できます。プロバイダーの文書化された仕組みを使用してWebhookの真正性を検証し、イベントスキーマを検証し、プロバイダーイベントIDまたは決定論的フィンガープリントで重複を排除し、副作用を繰り返さずに同じイベントの繰り返し配信を許可してください。生のペイロードは必要な場合にのみ保存し、暗号化、アクセス制御、保持期間の制限を行います。正規化された状態では、受付済み、サーバー配信済み、バウンス、苦情、遅延、拒否、サプレッションの結果を区別する必要があります。
サプレッションを送信時の制御にする
サプレッションのレコードは、ダッシュボードに表示するだけでなく、プロバイダーへの送信のたびに確認するべきです。恒久的にバウンスしたアドレスや苦情は通常サプレッションが必要ですが、一時的な配信遅延には別のポリシーが必要です。サプレッションの範囲は意図を持って決めてください。アカウント全体のリストは共有のレピュテーションを守れますが、あるテナントの受信者の結果が別のテナントの送信をブロックしてしまうことがあります。テナント単位のリストはこの結び付きを弱めますが、それでも不正利用とプラットフォームの安全のための層は必要です。理由、元となったイベント、テナント、作成日時、管理された削除手段を記録します。苦情や恒久的なバウンスによるサプレッションの解除は影響が大きいため、意図的なレビューと、アドレスが有効で受信者がそのメッセージを求めているという証拠を必須にするべきです。受信者の生のアドレスを一般的なログや実験にコピーするのは避けましょう。送信ポリシーの強制は運用用のストレージで行い、アナリティクスでは集計値を使えます。
バッチ送信と機密性の高い業務フローを保護する
バッチ用エンドポイントは、認可やバリデーションの誤りの影響を何倍にも広げます。すべての項目に同じドメイン所有権、サプレッション、サイズ、コンテンツのチェックを適用し、バッチの長さに厳格な上限を設け、他のテナントのデータを漏らさずに項目ごとの結果を返してください。レート制限は、認証情報、ワークスペース、ドメイン、プロバイダーの各レベルに設け、バーストと一定期間の送信量には別々の制御を用意します。1つのリクエストに多数の受信者が含まれうるため、全体で1つの秒間リクエスト数の上限だけでは不十分です。エージェントが操作するツールでは、影響の大きいバッチを送信する前に意図的な確認を必須にしましょう。同意や運用ルールが異なる場合は、トランザクションメールとマーケティングメールの権限を分けてください。受信者の異常な増加、繰り返し拒否されるドメイン、バウンスや苦情の大きな変化、キーの急速な作成を監視します。レート制限は安全性を支えますが、認証、オブジェクトの認可、確認済みの同意、不正利用への対応に代わるものではありません。
本番運用の前に失敗時の経路をテストする
プロバイダーのシミュレーターや管理されたメールボックスを使って、受け付け、受信サーバーへの配信、ハードバウンス、苦情、遅延、無効なドメイン、失効したキー、スロットリング、プロバイダーのタイムアウト、Webhookの重複、キューの再配信をテストします。同じ冪等キーでアプリケーションのメッセージが1つだけ作成されること、再送されたイベントで副作用が重複しないこと、あるテナントが別のテナントのドメインやメッセージIDで読み取りや送信ができないことを確認してください。実際に受信したメッセージについて、From、Return-Path、DKIM、SPF、DMARCアライメント、テキストとHTMLのレンダリング、該当する場合は配信停止の挙動、リンクを調べます。キューの負荷テストは承認されたプロバイダーの上限以下で行い、バックプレッシャーを迂回するのではなく、正しく機能することを確認しましょう。キューの滞留時間、再試行の使い切り、イベント取り込みの失敗、クォータの余裕、バウンスと苦情の変化、プロバイダーからのコールバックの欠落にアラームを設定します。リリースのチェックリストでは、各アラートと復旧手順の担当者を明記してください。
SendHQでこのパターンを慎重に適用する
SendHQは、ワークスペース単位のBearerキー、検証済みFromドメインチェック、単一および一括メッセージ作成、受信インボックス、メッセージイベント、サプレッションリソースを提供します。これらの機能はこのガイドのアーキテクチャを支えます。キーはサーバー側に保持し、メッセージリソースを作成し、そのIDを保持して、初期レスポンスを最終配信として扱うのではなく後続イベントを読み取ります。プラットフォームにかかわらず、呼び出し元は意図した受信者、合法かつ想定されたメール、コンテンツの正確性、影響の大きい送信の慎重な承認に引き続き責任を負います。
よくある質問
メールAPIはWebリクエストの中で同期的に送信すべきですか?
通常はおすすめしません。永続的なアプリケーションのメッセージを作成してキューに入れ、ワーカーにプロバイダーを呼び出させます。これによりレイテンシーを切り離せ、上限付きの再試行が可能になり、プロバイダーの結果があいまいな場合の突き合わせも容易になります。
リクエストがタイムアウトしたとき、メールの重複送信を防ぐには?
安定したアプリケーションのジョブIDと、一意性制約を伴う冪等性の境界を使います。結果があいまいなタイムアウトでは、新しい識別子でプロバイダーに再送信する前に、既存のジョブを突き合わせてください。
メールAPIのレスポンスが成功なら、配信されたということですか?
いいえ。通常は、APIまたはプロバイダーがリクエストを受け付けたことを示すだけです。後から届くイベントを使って、最初の受け付けとは別に、受信サーバーへの配信、バウンス、苦情、遅延、拒否、サプレッションを区別してください。
メールAPIにはどのDNSレコードが必要ですか?
必要なレコードはプロバイダーによって異なりますが、本番環境での送信には一般に、ドメイン検証とDKIMに加え、正しいSPFの方針と、正規の送信ストリームとアライメントしたDMARCポリシーが必要です。
APIキーをブラウザのコードに保存してもよいですか?
いいえ。ワークスペースとプロバイダーの認証情報はサーバー側の秘密情報ストレージに置き、アプリケーションのAPIキーは可能な限りハッシュ化して保存し、完全な秘密情報は一度だけ表示し、迅速に失効・ローテーションできる手段を用意してください。
メールAPIは恒久的なバウンスをどう扱うべきですか?
プロバイダーのイベントを正規化してアプリケーションのメッセージに対応付け、意図した範囲内でその受信者への以後の通常送信をサプレッションします。解除は意図的に行い、証拠に基づくべきです。
出典
- RFC 9110:HTTPのセマンティクス — Internet Engineering Task Force
- RFC 5321:簡易メール転送プロトコル(SMTP) — Internet Engineering Task Force
- RFC 5322:インターネットメッセージ形式 — Internet Engineering Task Force
- RFC 6376:DomainKeys Identified Mail(DKIM)署名 — Internet Engineering Task Force
- RFC 7208:Sender Policy Framework(SPF)の仕様 — Internet Engineering Task Force
- RFC 7489:ドメインベースのメッセージ認証・報告・適合(DMARC) — Internet Engineering Task Force
- Amazon SESの送信アクティビティの監視 — Amazon Web Services
- Amazon SESの通知に関するトラブルシューティング — Amazon Web Services
- OWASP API Security Top 10 2023(APIセキュリティの10大リスク) — OWASP Foundation
- SendHQのOpenAPI契約 — SendHQ