SESのバウンスレート上昇を事前に防いだお話

## はじめに
私が所属するプラットフォームSREでは、会員マイクロサービスと認証マイクロサービスが利用するAWS SES（以下、SES）を主管しています。
SESを運用するにあたって、バウンスレートは気をつけるべき重要な項目の1つです。
本記事は、バウンスレートが上昇しないよう少し頑張ったお話になります。

## （前提）SESのバウンスレート
> バウンス率が 5% 以上になると、アカウントはレビュー対象になります。バウンス率が 10% 以上の場合は、高いバウンス率の原因となった問題が解決するまで、以後の E メール送信を一時停止することがあります。
https://docs.aws.amazon.com/ja_jp/ses/latest/dg/faqs-enforcement.html

上記の通り、SESのバウンスレートは、5%以下に抑える必要があります。10%を超えると、対象のAWSアカウントがレビュー対象になります。10%を超えてもすぐに使用できなくなるわけではないですが、レビュー期間中（時間的なものでなく、今後のメール送信130,000通以内）に5%以下へ戻す必要があります。

バウンスレートをこれ以上あげないようにする対策としては以下が案内されています。

- バウンス率を高くしているアドレスへのEメールの送信を停止する
- アカウントレベルのサプレッションリストをオンにする

https://repost.aws/ja/knowledge-center/ses-high-bounce-rate

ただし、残念ながら、下げる方法は上記では案内されていません。
公式には案内されていませんが、すでに届くと判明しているメールアドレスに送信しまくる、というのは1つの解決方法になりえるかもしれません。

以前、stg環境のAWSアカウントのSESが40%までいってしまったことがあります。原因は、負荷試験で存在しないメールアドレス宛にメールを送信しまくってしまったことです。この時は、サポートケースで負荷試験を実施していたという背景を伝え、今後はモックを利用する方針を説明したことで、数日後にバウンスレートを元の0％に戻していただけました。

## 背景（不穏な気配を感じとる）
ZOZOには、dev環境やstg環境上でQAや開発用にダミーユーザーを作成するツールが存在します。そのツールに、大量のダミーユーザーのアカウント登録をできる機能が新しく追加されたという情報を聞きました。これは、`@zozo.com`ドメインの前の文字列と作成するアカウント数を入力してアカウントを作成するものです。もし、存在しないメールアドレスが指定されると、ZOZOTOWNの仕様上、そのメールアドレス宛に本人確認メールを飛ばすため、SESのバウンスレートが大幅に上昇してしまう可能性がありました。
そこで、そのツールの開発チームと会員マイクロサービスのバックエンドチームと認証マイクロサービスのバックエンドチームを含めて、課題感の共有と対応の検討を行いました。

## 対応
### 暫定対応
まずは暫定対応として、そのツールのメールアドレスを入力する項目の近くに、「実在するメールアドレスおよびそのエイリアス以外のメールアドレスは使用しないでください。」と注意書きをするように改修してもらいました。あくまで注意書きなので、守られるとは限りませんが、一定の効果はあると考えました。
エイリアスとは、`@zozo.com`ドメインの前の文字列に+をつけることです。具体的には、`hoge@zozo.com`が実在する場合、エイリアスは`hoge+@zozo.com`です。
恒久対応完了までにある程度の時間がかかっため、結果的にこの対応は吉でした。バウンスレートの上昇も発生しませんでした。

### 恒久対応
恒久対応として、バックエンドチームへ、基本的にprd環境以外はモックを使うように提案しました。環境変数で手軽にメール送信処理をモックへ切り換えられるようにしておけば、実際にメール送信を伴う動作確認をしたい場合でも問題ないと考えました。しかし、バックエンドチームに拒否されてしまいました。理由は、以下でした。

- stgとprd環境が異なる構成になってしまうの避けたいから
- 対象のアカウントに本人確認メールや登録完了メールが届かないことによる、機能的な制限がないことを断言できないから。モックにしたことで他チームのQAに支障をきたす可能性をあげることは避けたい。
  - メール本人確認の工程をスキップして、本人確認済み状態にする仕組みがすでにあり、運用されていることを伝えたが納得してもらえなかった。
  - 打ち合わせ後、ツール開発チームに確認して、「本機能で作成するアカウントはあくまで負荷試験用のダミーアカウントであり、このアカウントを使ってQAや動作確認をするわけではないため、影響はないはず」との回答をいただいた。この回答をバックエンドチームに伝えたところ、納得してもらえた。

しかし、1つ目の懸念（不安）は残ったままです。
これを解消するにあたって、ツールで登録できるメールアドレスのドメインを`@example.com`に限定して、アプリケーション側で`@example.com`宛の時だけモックにしてもらうように提案しました。すると、`@example.com`に限定するのは良さそうだが、SESの[送信承認ポリシー](https://docs.aws.amazon.com/ja_jp/ses/latest/dg/sending-authorization-policy-examples.html)により、そのドメイン宛のメール送信を拒否するのはどうか、と提案いただきました。送信承認ポリシーを検証した結果、意図通りに動作し、ハードバウンスとして扱われないことを確認できたため、この提案を採用しました。恒久対応をまとめると、以下の通りです。

- SREチーム
  - SESの送信承認ポリシーで`@example.com`宛の送信を拒否する設定をする
- バックエンドチーム
  - `@example.com`宛の場合はメール送信処理を行わない
  - SESの送信承認ポリシーでエラーが発生した場合は、アプリケーション側で200レスポンスを返すように改修する
  - （上記どちらか一方で十分だが、デグレが発生した場合なども考慮してどちらも実装することにした）
- ツール開発チーム
  - ツールで登録できるメールアドレスのドメインを`@example.com`に限定する改修を行う
  - 暫定対応の注意書きを削除する

### 他にも検討したこと
ツール側で、ZOZOTOWNの会員登録APIを叩く前に、入力されたメールアドレスの存在確認をする機能を追加できないか考えました。調査したところ、難しそうでした。メールサーバーに対して対象メールアドレスを引数にVRFYコマンドを実行するという方法があるみたいですが、セキュリティ上、VRFYコマンドを許可していないメールサーバーが多いそうです。SESも許可していないみたいです。

また、モックの代わりとして、レガシーシステムが利用しているオンプレのメールサーバを利用する、メールサーバを自前で構築してdev/stgではそれを利用する、なども考えましたが、有力な案にはなり得ませんでした。

## SESの送信承認ポリシー設定方法
設定方法をまとめておきます。

なお、ZOZOではSESをIaCで管理しない方針しているため、AWSコンソールから設定しました。IaC管理していない理由は、構築当初はCloudFormationがSESの一部リソースしかサポートしていなかったためです。Terraformで構築することも可能でしたが、AWSリソースはTerraformで管理しない方針にしています。また、SESは一度構築すると変更することはほぼないため、IaCで管理するメリットが少ないと考えました。

送信承認ポリシーの設定は、`AWSコンソールのSESを選択＞設定のIDを選択＞検証済みIDを選択＞承認タブを開く＞ポリシーの作成＞カスタムポリシーの作成`を選択して進みます。

![Notification_Center.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221948/481040f8-f822-28b8-fa1f-4dc7245dc076.png)

そして、以下のポリシードキュメントを追加します。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Deny",
            "Principal": "*",
            "Action": [
                "ses:SendEmail",
                "ses:SendRawEmail"
            ],
            "Resource": "arn:aws:ses:ap-northeast-1:xxx:identity/xxx",
            "Condition": {
                "ForAnyValue:StringLike": {
                    "ses:Recipients": "*@example.com"
                }
            }
        }
    ]
}
```

AWS CLIでメール送信をして、動作確認をしました。
以下は、`@example.com`ドメイン宛に送信して、送信失敗する例です。

```
% aws ses send-email \
    --from "no-reply@xxx" \
    --destination "ToAddresses=xxx@example.com" \
    --message "Subject={Data=Test Email,Charset=utf-8},Body={Text={Data=This is a test email sent using AWS CLI.,Charset=utf-8}}"

An error occurred (AccessDenied) when calling the SendEmail operation: User `arn:aws:iam::xxx:user/xxx' is not authorized to perform `ses:SendEmail' on resource `arn:aws:ses:ap-northeast-1:xxx:identity/xxx'
```

以下は、存在する`@zozo.com`ドメイン宛に送信して、送信成功する例です。

```
% aws ses send-email \
    --from "no-reply@xxx" \
    --destination "ToAddresses=xxx@zozo.com" \
    --message "Subject={Data=Test Email,Charset=utf-8},Body={Text={Data=This is a test email sent using AWS CLI.,Charset=utf-8}}"

{
    "MessageId": "01060191c025b0ad-5d81c3f3-9b84-4aaf-894c-9c6f7859fbf3-000000"
}
```

意図通りに、`@example.com`宛のメール送信が拒否され、他のドメイン（`@zozo.com`）宛のメール送信が成功しました。

## さいごに
本記事では、SESのバウンスレート対策の必要性や、事前に対策した話をまとめました。
メールサーバの運用をしている方の参考になれば幸いです。