AWS SES。もう自前バウンス対応は不要かも。

[KINTO Technologies Advent Calendar 2021 - Qiita](https://qiita.com/advent-calendar/2021/kinto-technologies) の1日目の記事です。

2021年12月現在、AWS SESでバウンスを処理するためには、どんな作業が必要/不要なのか。
AWSのドキュメントやサポートに確認した内容に基づいて説明します。

## TL;DR
**基本的に** バウンス処理を自前で構築する必要はありません。

特に、**バウンスレートを下げる**ことだけが目的の場合は、

+ Account-level Suppression List が有効かどうか確認するだけでOKです。
+ AWS SNSからバウンス情報を取得して処理したり、自前のRDB/NoSQLを用意してメールアドレスのリストを保存することは不要です。

最新情報に基づいて設計を見直し、**車輪の再発明はやめましょう**。

## この記事を読むために必要な前提知識

+ メールのバウンスとはなにか
+ メールを送るときは、バウンスと苦情の処理がセットで必要であること
+ バウンスには、ハードバウンスとソフトバウンスの２種類があること

## Suppression List とは？
バウンスを適切に処理しようとすると、バウンスしたメールアドレスを溜めておくためのリストが欲しくなるかもしれません。
でも、リストを**自前DBで構築しないでください**。
SESがすでに Suppression List という名前で提供してくれています。
Suppression List には、Global Supression List と Acount-level Suppression List の2種類があります。

### Global Suppression List

+ 2013年5月から提供されています。
+ 全てのAWSアカウントで共通のリストです。
+ リストに登録済みのメールアドレスへの送信を試みると、**バウンスレートが上がります**。
+ メールアドレスは最大で14日間保存され、その後削除されます。
+ 登録されたメールアドレス一覧をAPI等で取得することは出来ません。
+ SESを使う限り、**利用必須** のリストです。
    + 利用停止はできません。

### Account-level Suppression List

+ 2019年11月から提供されています。
+ 単一のAWSアカウントの中でSESを利用している単一リージョンごとのリストです。
+ リストに登録済みのメールアドレスへの送信を試みても、**バウンスレートは上がりません**[^HardBounceRateOfAccountLevel]。
+ メールアドレスは自動削除されません。
+ 登録されたメールアドレス一覧をAPIで取得することが**出来ます**。
+ 利用は任意です。
    + 2019年11月25日以降にSESを利用開始した場合は、**デフォルトで有効** になっています。

### 2つの Suppression List の共通点

+ ハードバウンス（と苦情）は **自動的に** 登録されます[^HardBounceOfGlobal] [^HardBounceOfAccountLevel]。
    + SNS・SQS・処理バッチ等をセットアップする必要はありません。
+ ソフトバウンスは登録されません。
+ リストに登録されていれば、実際のメール送信は行われません。
+ リストに登録されていても、メール送信は成功したように見えます。
    + ちなみに、2013年5月まで提供されていた Blacklist は、メール送信時に失敗する仕様でした[^Blacklist]。

## バウンスレートを下げるために適切な構成は？
2つの Suppression List の仕様を理解すると、基本的にデフォルトのままで問題ないことがわかります。

### Account-level Suppression List を使うべき
必須の Global Suppression List に加えて、任意の Account-level Suppression List も使うべきです。

なぜなら、Global Suppression List だけに登録されたメールアドレスへ送信を試みるとバウンスレートが上がるのに対し、Account-level Suppression List **にも**登録されている場合はバウンスレートは上がらないからです。

使い方は簡単です。
Account-level Suppression List は、2019年11月25日以降にSESを利用開始した場合は、デフォルトで有効になっています。
有効になっていなくても、1度だけAPIを叩けば有効にすることができます。
他に必要な作業はありません。

### SNSによるバウンス情報受け取りは不要

SNSを構成してバウンス情報を受け取る必要はありません。
なぜなら、Account-level Suppression List に（自動的に）登録されたメールアドレスは、APIで取得できるからです。

## もう不要かもしれない AWS SNS や 自前DB構成の見直しを

SNS (+ SQS) + EC2/ECS/Lambda でバウンスを処理したり、RDB/NoSQL を使って自前の Suppression List を構築しているシステムは多いのではないでしょうか。

たしかに、Global Suppression List だけ提供されていた時期（2013年5月〜2019年11月）は、リストに登録されてしまったメールアドレスを確実に送信先から除外する必要があり、それを実現するための唯一の正しい構成でした（そうしないとバウンスレートが上がってしまいました）。

でも、当時と比べてSESは進化しているので、このような構成は不要になっているかもしれません。
要件/目的と照らして、一度見直しましょう。

### バウンスされた事実を**リアルタイム**に把握して、サービスの運用者/利用者にフィードバックしたい
プッシュ型で把握する方法は現在でもこれしかありませんので、**この目的のためにSNSを構築するのは妥当**です。
なお、SNS + Lambda の構成では、間にSQSを挟むほうが望ましいです。
自動リトライの恩恵を受けられるため[^SNSSQSLambda]です。

### ハードバウンスの種類・回数・時刻を厳密に把握したい
Account-level Suppression List からはこれらの情報を取得できませんから、**この目的のためにSNSを構築するのも妥当**です。
しかし殆どのシステムでは、厳密に把握する必要はないと考えられるので、仕様を再検討できるのではないでしょうか。

### ソフトバウンスを再送したい
ソフトバウンスはどちらの Suppression List にも登録されないものの、SESが勝手に再送してくれます[^SoftBounceResend]から、**この目的のためにSNSを構築するべきではありません**。
なお、ソフトバウンスは最大12時間ほど再送が指数関数的バックオフでリトライ[^SoftBounceResendExponential]され、それでも成功しなかったときにようやく、SNSでバウンス情報を送ってくるようです[^SoftBounceResend]。

### メールを送信する前にリストをチェックして、プログラムレベルで送信を防ぎたい
Account-level Suppression List なら自動でハードバウンスが登録され、かつ、APIでアクセスできるので、**SNSも独自DBも不要**です。

## まとめ

今回、数年ぶりにSESでバウンスレートを下げるための検討/調査を行い、「自前処理は基本不要」という結論に至るまで少々時間がかかりました。
私自身が Global Suppression List だけが提供されていた頃の古い知識/経験にとらわれていたし、ドキュメントからは明確に読み取れない重要なポイント[^HardBounceOfAccountLevel] [^SoftBounceResend]もあったためです。
時間をかけて念入りに調査した分、正確性は上がっていると思いますので、お役立ていただけると幸いです。

また、2021年2月から Amazon SES の管理コンソール画面やドキュメントが一新され、従来のものには Amazon SES **Classic** という名称（レトロニム）が与えられていたことがわかりました。
そのため、**Classic** の機能は変更/廃止になったのかどうかの確認も必要でした。
どうやら機能追加だけだったようですが、旧 SES API も **Classic** に含まれ、廃止までの期間限定（ただし廃止時期は未定）とされているので、SES API v2 への移行は近いうちに済ませたほうが良いかもしれません。

SESに限らず、AWSには便利な機能がどんどん追加されています。
最新情報を調べることで、無駄な作業を避け、本質的な開発に注力したいですね。

当社では、トヨタ車のサブスク「KINTO」等の企画/開発を行っており、エンジニアを募集中です。
[KINTO Technologies コーポレートサイト](https://www.kinto-technologies.com/)

[^SNSSQSLambda]: [SNS to Lambda vs SNS to SQS to Lambda への回答](https://stackoverflow.com/a/42762545/5522950) 

[^HardBounceRateOfAccountLevel]: [Using the account-level suppression list](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/sending-email-suppression-list.html) より、"Amazon SES doesn't count the messages that you send to addresses on the account-level suppression list toward the bounce rate for your account."。Account-level Suppression List に登録されているメールアドレスに送信試行しても、バウンスレートに影響しないことが明記されています。

[^HardBounceOfGlobal]: [Using the Amazon SES global suppression list](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/sending-email-global-suppression-list.html) より、"When any Amazon SES customer sends an email that results in a hard bounce, Amazon SES adds the email address that produced the bounce to a global suppression list."。Global Suppression List では、ハードバウンスは自動で登録されることが明記されています。

[^HardBounceOfAccountLevel]: [Using the account-level suppression list](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/sending-email-suppression-list.html) より、"When you configure the account-level suppression list, you specify whether addresses should be added to the list when they result in hard bounces, when they result in complaints, or both. You can manually add or remove individual or bulk addresses from the account-level suppression list by using the Amazon SES API v2."。Account-level Suppression List のほうは少し分かりづらくて、手動またはAPIを使ったバルク処理でのみ、ハードバウンスされたアドレスが追加されるものと誤解しそうなのですが、実動作とAWSサポートへの問い合わせで、「自動追加されること」を確認しました。

[^Blacklist]: [Goodbye blacklist. Introducing the suppression list!](https://aws.amazon.com/jp/blogs/messaging-and-targeting/goodbye-blacklist-introducing-the-suppression-list/) より、 "The main difference between the blacklist and the suppression list is that if you tried to send an email to an address on the blacklist, the call was rejected with an “Address Blacklisted” error. Now, the call to Amazon SES succeeds, but Amazon SES treats the email as if it was a hard bounce."。Blacklist に登録されていたメールアドレスへのメール送信のAPIコールは失敗する仕様だったことがわかります。

[^SoftBounceResend]: [Amazon SES email-sending process](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/send-email-concepts-process.html)より、"Soft bounce—The ISP cannot deliver the email to the recipient because of a temporary condition, such as the ISP is too busy to handle the request or the recipient's mailbox is full. A soft bounce can also occur if the domain does not exist. The ISP sends a soft bounce notification back to Amazon SES, or, in the case of a nonexistent domain, Amazon SES cannot find an email server for the domain. In either case, Amazon SES retries the email for an extended period of time."。なお、[What is considered a soft bounce on Amazon SES, and how can I monitor soft bounces?](https://aws.amazon.com/premiumsupport/knowledge-center/ses-understand-soft-bounces/?nc1=h_ls) の "Generally, soft bounces should be retried after some time because they are caused by temporary issues." からは、SESではなくAWS利用者がリトライをすべきという印象を受けますが、AWSサポートの回答は「"SESが"リトライする」とのことでした。

[^SoftBounceResendExponential]: [Does SES resend email if soft bounce occurs?](https://forums.aws.amazon.com/thread.jspa?threadID=110939) より、"When attempting to deliver an email, Amazon SES will continue making delivery attempts until receiving a successful response, or until 12 hours elapse. If the receiving ISP returns a temporary error code (e.g., a 4xx SMTP code), then SES will keep trying. There is no limit to the number of attempts; however, SES will apply exponential backoff between retries for up to 12 hours."。