SESでメール一括送信時に考慮すべきことなどについて ～AWS100本ノック～ 18/100

# はじめに

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/1caf7f15-1ce4-5cb8-4c14-8d9f64149cd5.png)

AWS SESを利用してメールの大量送信を行う場合、`メールテンプレート`と呼ばれる件名と本文を定義したテンプレートファイルを作成し、SendBulkEmailというAPIで一括送信することになります。
※1件ずつ送ることも出来ますが、処理効率的にメールテンプレートを使うのが良いと思います。

以下は、テンプレート作成のAWS CLIコマンドのサンプルです。
`{{xxx}}`形式になっている部分は、変数になります。
名前などのユーザ毎に変わる値を変数にして、メール一括送信時に置換することでメール件名と本文の一部を変更することが出来ます。
```json:test-template.json
{
    "Template": {
        "TemplateName": "test-template",
        "SubjectPart": "こんにちは {{name}}さん",
        "TextPart": "こんにちは {{name}}さん\r\nこれはテストメールです。"
    }
}
```

以下はメール一括送信のAWS CLIコマンドのサンプルです。
`test@example.com`からメールをBulkEmailEntriesに指定した件数分(今回は2件)送っています。
```sh
aws ses create-template \
  --cli-input-json file://test-template.json
```

```json:test-template-data.json
{
  "FromEmailAddress": "test@example.com",
  "DefaultContent": {
    "Template": {
      "TemplateName": "test-template-only-text"
    }
  },
  "BulkEmailEntries":[
    {
      "Destination":{
        "ToAddresses":[
          "success@simulator.amazonses.com"
        ]
      },
      "ReplacementEmailContent": {
        "ReplacementTemplate": {
          "ReplacementTemplateData":"{ \"name\":\"user1\" }"
        }
      }
    },
    {
      "Destination":{
        "ToAddresses":[
          "bounce@simulator.amazonses.com"
        ]
      },
      "ReplacementEmailContent": {
        "ReplacementTemplate": {
          "ReplacementTemplateData":"{ \"name\":\"user2\" }"
        }
      }
    }
  ]
}
```

```sh
aws sesv2 send-bulk-email \
  --cli-input-json file://test-template-data.json
```

と、簡単に実行方法について紹介しましたが、実際にアプリケーションで利用するとなった場合、色々考慮する必要が出てきます！

なので今回は以下の3つの視点で、説明していきます。
- SESで考慮したほうが良いこと
- 呼び出し元で考慮したほうが良いこと
- それ以外で考慮したほうが良いこと

ざっくり、全体をまとめた図が以下になります。

![メール送信-SESまとめ.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/f9da8cbe-abc9-44e8-a216-9d1a6db06da0.png)



# SESで考慮したほうが良いこと
まず、SESで考慮したほうが良いこと・やるべきことについて紹介します。

## 送信元の検証
当たり前ではありますがメールを送信する際には、送信元を指定する必要があります。
しかし、どんな送信元でも指定するということではありません。

送信元には、以下のどちらかを指定できます。
- 検証された`ドメイン`のメールアドレス ※Route53へのレコード追加が必要
  - 例えば、ドメインが`hoge-example.com`であれば、`sender@hoge-example.com`や`reply@hoge-example.com`から送れます
- 検証された`メールアドレス`
  - 承認メールが飛ぶので、`24時間以内に`承認してもらうことが必要

## サンドボックスの解除
SESはデフォルトだと、`サンドボックス`と呼ばれる制限がかかった状態になっています。
サンドボックスだと、
- 不特定多数に送れない(検証済みのメールアドレスにしか送れない)
- 1秒あたり1件までしかメールを送れない
- 1日当たり200件までしかメールを送れない

といった制限があります。
なので、本番環境のアプリケーションを構築するAWSアカウントでは必ず解除する必要があります。

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/db4db41e-5949-d2a3-dce7-1b780152182e.png)

:::note info
開発環境など不特定多数に送れてしまうとリスク(開発者が誤って利用できるメールアドレスを登録してしまってメールが飛んでしまう、など)があります。
なので、開発環境ではサンドボックスのまま運用して、必要なら解除するのが良いと思います。
:::

## 制限の緩和
サンドボックスを解除すると、サンドボックスの制限が以下のように緩和されます。

- 不特定多数に送れない(検証済みのメールアドレスにしか送れない)👉不特定多数に送れる(検証する必要がない)
- 1秒あたり1件までしかメールを送れない👉1秒あたり14件送れる
- 1日当たり200件までしかメールを送れない👉1日当たり50,000件送れる

:::note warn
サンドボックスを解除しても、送信元に関しては、検証済みのドメイン or メールアドレスである必要があります。
:::

メール一括送信では、[SendBulkEmail](https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-sesv2-2019-09-27.html#sendbulkemail)というAPIを利用することになるのですが、一括送信では一度に`50送信先まで`送れます。
なので、1秒あたり14件だと足りないかもしれません。
そういった場合は、`Service Quotas`で上限緩和の申請を行いましょう。
※無料のベーシックプランでも、1日前後で対応してくれると思います。

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/9faf31c1-83c7-188e-f677-86ee0002a4a1.png)

![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/ce518b50-d0e3-95a4-265a-63ed64d73cc9.png)


:::note warn
緩和申請を行うと自動的にサンドボックスが解除されてしまいました...:cry: 
上限緩和は行いたいが、不特定多数には送れないようにしたい場合はサンドボックスを維持する必要があるので、そういった場合は上限緩和申請後に追加でその旨をメッセージで伝えると対応してくれるはずです。
:::
※以下の画像は、自動的にサンドボックスが解除されたので、上限緩和はしつつ、サンドボックスに戻してもらった時のメッセージです

![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/872ec17e-b569-374d-9aff-03cdb330bb7d.png)

## ログの収集
メール一括送信を実行したら、ちゃんと送れたか知りたくなりますよね？:smile: 
実際にメールは色々な理由で、届かなかったり遅延したりすることがあります。

- メールアドレスが間違っていた
- 受信者のメールボックスがいっぱいだった
- 送信先のメールサーバの何かしらの問題で遅延した
- SESがメールにウイルスが含まれていると判断し、配信しなかった

SendBulkEmailのリクエストが成功したら、ユーザに必ずメールが届いたと思ってしまう人もいるのですが、実は違います。
あくまで、SendBulkEmailの成功は`SESが一括送信リクエストを受け付けただけ`、であり実際に配信が成功したかはわかりません。
また`存在しないメールアドレスの場合`、SendBulkEmail実行がエラーになると思う人が多いのですが、実際はリクエストが成功します ※SESからの配信時にバウンスします

じゃあ、どうすれば配信が成功・失敗したかわかるかというと、`送信イベント`を収集し、CloudWatchにログを出力するなどして、判断する必要があります。

ログの収拾については、[あの人へSESで送ったメールってどうなったんだろう？どうやれば確認出来るのか ～AWS100本ノック～ 10/100](https://qiita.com/namasa/items/4a19920a5bb0afb8a835)という記事で紹介しています。

▼送信イベント

| 大分類 | イベントタイプ |  | 概要 |
|:-----------|:------------|:------------|:------------|
| 送信と配信 | Send | 送信 | 呼び出しが成功し、Amazon SES は受信者のメールサーバーにメッセージを配信しようとしています。 |
|  | RenderingFailure | レンダリングの失敗 | テンプレートのレンダリングの問題により、メッセージは送信されませんでした。 |
|  | Reject | 拒否 | Amazon SES はメッセージを受け入れましたが、ウイルスが含まれていると判断し、受信者のメールサーバーに配信しようとしませんでした。 |
|  | Delivery | 配信 | Amazon SES は、受信者のメールサーバーにメッセージを正常に配信しました。 |
|  | Bounce | ハードバウンス | 受取人のメールサーバーが E メールを完全に拒否しました (Amazon SES が複数回試行してもメールを配信できない場合、ソフトバウンスが含まれます)。 |
|  | Complaint | 苦情数 | メッセージは受信者のメールサーバーに正常に配信されましたが、受信者はスパムとしてマークしました。 |
|  | DeliveryDelay | 配信の遅延 | 一時的な問題が発生したため、メッセージを受信者のメールサーバーに配信できませんでした。 |
|  | Subscription | サブスクリプション | E メールは受信者に正常に配信されました。受信者は、[リスト-サブスクリプション解除] ヘッダーをクリックするか、E メールのフッターにリンクされた [サブスクリプション解除] ウェブページを使用して、サブスクリプションの設定を更新しました。 |
| 開封とクリックの追跡 | Open | 開封数 |  |
|  | Click | クリック数 |  |


:::note info
現在、送信イベントを収集するためのAWSサービスとしては以下が用意されています。
- CloudWatch
- Kinesis Data Firehose
- Pinpoint
- SNS
- EventBridge
:::


# 呼び出し元で考慮したほうが良いこと
呼び出し元のアプリケーションで考慮したほうが良いこと・やるべきことについて紹介します。

## メールテンプレートの管理
一括送信で利用するメールテンプレートですが、以下のようにユースケースによって管理方法が変わると思っています。

- インフラ担当者が登録/編集する、アプリケーションの管理者は閲覧・編集が出来なくていい
👉インフラ構築で利用する`CDK(CloudFormation)`で管理する

- アプリケーションの管理者は閲覧・編集をしたい
👉アプリケーションでメールテンプレート管理をし、`AWS SDK`を利用してメールテンプレートを閲覧・編集する

## エラー対策

次に、SendBulkEmailを実行した際のエラー対策になります。

SendBulkEmailに関しては、エラーとしては大きく以下の2パターンあります。
- `SendBulkEmailリクエスト自体が失敗する場合`
- `SendBulkEmailリクエストは成功するが、あるメール送信が失敗する場合`

では、それぞれについて説明します。


### SendBulkEmailリクエスト自体が失敗する場合

全体が失敗するようなケースの場合は、以下のいずれかのSESの例外が投げられます。

- TooManyRequestsException
- LimitExceededException
- AccountSuspendedException
- SendingPausedException
- MessageRejected
- MailFromDomainNotVerifiedException
- NotFoundException:
- BadRequestException:

全体が失敗するようなケースっていつだろう？:thinking:って話ですが、`51件メールを送信しようとした場合`などがあります。
SendBulkEmailの仕様として、1リクエストで最大50の宛先をして出来る仕様(1つの送信で宛先が1つなら50件まで)があります。
なので、51件メールを送信しようとしたら全体が失敗します。

### SendBulkEmailリクエストは成功するが、あるメール送信が失敗する場合

リクエストが成功した際のレスポンスとして、以下のような`BulkEmailEntries[]`が返却されます。
これを見ることによって、各送信の成功・失敗が`Status`から判断できます。

また、`リクエストのBulkEmailEntries[]で指定した配列要素数分`、BulkEmailEntryResults[]が返されます。なので、30件メールを送信すればレスポンスも30件になります。
さらに、`リクエストのBulkEmailEntries[]で指定した配列と同じ順番で`、BulkEmailEntryResults[]が返されます。これで、各メール送信の結果を突合できます:smile:

```json
{
    "BulkEmailEntryResults　": [
        {
            "Status": "Success",
            "MessageId": "xxxxx-xxxxx-xxxxx-xxxxx-xxxxx-xxxxx-xxxxx"
        },
        {
            "Status": "Success",
            "MessageId": "xxxxx-xxxxx-xxxxx-xxxxx-xxxxx-xxxxx-xxxxx"
        },
        {
            "Status": "MessageRejected",
            "Error": "Email address is not verified. The following identities failed the check in region AP-NORTHEAST-1: ***@example.com"
        }
    ]
}
```

ここからBulkEmailEntryResultsの中身の説明になりますが、以下の3つのパラメータから構成されます。
個別の送信が失敗した場合は、BulkEmailEntryResultsのStatusが`Success以外`になります。

▼BulkEmailEntryResults
| パラメータ | 概要 | 値 | 例 |
|:-----------|:------------|:------------|:------------|
| Status | 実行結果のステータス | ・正常：SUCCESS<br>・異常：SUCCESS以外 | ACCOUNT_THROTTLED |
| Error | 詳細のエラーメッセージ | ・SUCCESS：Error要素自体なし<br>・SUCCESS以外：エラーメッセージ | Email address is not verified. The following identities failed the check in region AP-NORTHEAST-1: ***@example.com |
| MessageId | SESが発行した一意なUUID | ・SUCCESS：UUID<br>・SUCCESS以外：基本的にMessageId要素自体なし<br><font color="red">※TRANSIENT_FAILUREなどの異常系の場合に、MessageIdが発行されることがある</font> | 0106019385436fd3-8bd938e5-c5e5-4069-be33-29486930dd6b-000000 |

各Statusは以下のようになります。
▼Status
| 分類 | Status | 再実行で送信できる可能性があるか | 概要 |
|:-----------|:------------|:------------|:------------|
| 正常 | SUCCESS | - | Amazon SESはメッセージを受け入れ、受信者への配信を試みます。 |
| 異常 | MESSAGE_REJECTED | なし | メッセージがウイルスを含んでいたため、拒否されました。 |
| 異常 | MAIL_FROM_DOMAIN_NOT_VERIFIED | なし | 送信者のメールアドレスまたはドメインが未検証です。 |
| 異常 | CONFIGURATION_SET_DOES_NOT_EXIST | なし | 指定された設定セットが存在しません。 |
| 異常 | TEMPLATE_DOES_NOT_EXIST | なし | 指定されたテンプレートが存在しません。 |
| 異常 | ACCOUNT_SUSPENDED | なし | メール送信に関連する問題のため、アカウントが停止されました。 |
| クォータ | ACCOUNT_THROTTLED | あり | アカウントが割り当てられた送信制限を超えたため、送信可能なメール数が制限されています。 |
| クォータ | ACCOUNT_DAILY_QUOTA_EXCEEDED | (日を跨がない限り)なし | アカウントから送信可能な1日あたりの最大メール数に達した、または超過しました。 |
| 異常 | INVALID_SENDING_POOL_NAME | なし | 指定された設定セットが存在しないIPプールを参照しています。 |
| 異常 | ACCOUNT_SENDING_PAUSED | なし | UpdateAccountSendingEnabled操作を使用して、Amazon SESアカウントでのメール送信が無効化されました。 |
| 異常 | CONFIGURATION_SET_SENDING_PAUSED | なし | UpdateConfigurationSetSendingEnabled操作を使用して、この設定セットでのメール送信が無効化されました。 |
| 異常 | INVALID_PARAMETER_VALUE | なし | この操作の呼び出し時に指定された1つ以上のパラメータが無効です。エラーメッセージで詳細情報を確認してください。 |
| SESの一時的な異常 | TRANSIENT_FAILURE | あり | 一時的な問題のため、Amazon SESはリクエストを処理できませんでした。 |
| 異常 or SESの一時的な異常 | FAILED | あり | Amazon SESはリクエストを処理できませんでした。エラーメッセージで詳細情報を確認してください。 |


個別の送信が失敗するようなケースっていつだろう？:thinking:って話ですが、
- 1件のメールで宛先を51件指定した場合
- リクエスト数が多すぎて1秒あたりの送信数上限に達してスロットリングした場合

などになります。

## 1秒あたりの送信上限数を超えた場合のエラーについて

おそらく、一番起こりやすいエラーは`1秒あたりの送信上限数を超えた時に起きるスロットリングエラー`だと思っています。
ただ、ドキュメントを見るとExceptionに`TooManyRequestsException`、BulkEmailEntryResultsに`ACCOUNT_THROTTLED`があり、どっちになるんだろう？:thinking: って感じです...

そこでAWSサポートに問い合わせてみたところ、対象のBulkEmailEntryResultsがACCOUNT_THROTTLEDになるとのことでした。

ちなみに...最大送信レートを一時的に超えることは可能なので、例えば1秒あたり50件の制限があったとしても、一時的に50件以上送ることは可能なようです。

## スロットリング予防
スロットリングした際は、上限緩和申請してリクエスト可能な数を増やすことで対応することも出来ますが、アプリケーション側でも`スロットリングしないようにする`、`スロットリングした際に再実行する`ような対応もしたほうが良さそうです。

### スロットリングしないようにする

**１．スリープ処理を入れる**
1,000件のメールを50件ずつ送信する場合、すぐに次のSendBulkEmailを実行すると1秒あたりの送信上限に引っかかってスロットリングになる可能性が高くなります。
そのため、各リクエストの間にスリープ処理 `※PHPだとsleep(1)`を入れるようにするのが良いと思います。

**２．バッチ処理による非同期化**
**３．キューによる非同期化**
この2つに関してはスロットリングとは関係ないのですが、大量のメール送信を同期的な処理で実行すると、長い時間同じ画面でユーザを待たせることになります。
なので、単一のメール送信ではない大量のメール送信は非同期化で行うのが良さそうです。
以下のイメージです。

![学ぶべきこと-メール送信の非同期化.drawio (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/a6526d4d-b809-a519-6370-d58263457e37.png)


### スロットリングした際に再実行する

なるべく、スロットリングしないように対処したいところですが、そうはいってもスロットリングは発生するかもしれません。

レスポンスのBulkEmailEntryResults[]で`StatusがACCOUNT_THROTTLED`の送信に関しては、少し待って再実行すれば成功するかもしれません。
なので、必要に応じてアプリケーション側で`リトライ処理`を組み込みましょう。
例：「ACCOUNT_THROTTLEDが発生したメッセージは1秒スリープして再度SendBulkEmailを実行」を最大3回行う

# それ以外で考慮したほうが良いこと
それ以外に考慮したほうが良いこと・やるべきことについて紹介します。

## バウンス・苦情対策

バウンス・苦情が一定の割合に達すると`レビュー対象`になります。具体的には、
- バウンス率が`5%以上`
- 苦情率が`0.1%以上`

:::note info
ソフトバウンスは、バウンス率には計上されません
:::

になるとレビュー対象になり、対応が求められます。
レビュー対象中もメールは引き続き送信できますが、以下のような場合一時停止になりメール送信が出来なくなります。

- アカウントが`レビュー対象`になっており、`レビュー期間の終了前に問題が修正されていない`
- バウンス率が`10%以上`
- 苦情率が`0.5%以上`
- アカウントが同じ問題について`複数回レビュー対象とされた`
- アカウントから送信されたEメールが`AWSのサービス条件に違反している`

バウンスには、大きく分けて以下の3種類があります。
- `Undetermined`：どのバウンスか判断できない
- `Permanent`：ハードバウンス
- `Transient`：ソフトバウンス

| bounceType | bounceSubType | 概要 |
|:-----------|:------------|:------------|
| Undetermined | Undetermined | 受取人の E メールプロバイダーはバウンスメッセージを送信しました。バウンスメッセージには、Amazon SES がバウンスの理由を判断できるだけの十分な情報が含まれていませんでし |
| Permanent | General | 受取人の E メールプロバイダーはハードバウンスメッセージを送信しました。**このタイプのバウンス通知を受信した場合は、受取人の E メールアドレスをメーリングリストから即座に削除してください。** |
|  | NoEmail | バウンスメッセージから受信者のメールアドレスを取得できませんでした。 |
|  | Suppressed | 受信者の E メールアドレスは、最近の履歴でハードバウンスを生じているため、Amazon SES サプレッションリストに追加されています。 |
|  | OnAccountSuppressionList | Amazon SES は、アドレスがアカウントレベルのサプレッションリストにあるので、このアドレスへの送信を抑制しました。これは、バウンス率のメトリクスに対してはカウントされません。 |
| Transient | General | 受取人の E メールプロバイダーは一般的なバウンスメッセージを送信しました。メッセージのバウンスを生じた問題が解決された場合、将来、同じ受取人にメッセージを送信できる可能性があります。 |
|  | MailboxFull | 受取人の E メールプロバイダーは、受取人の受信トレイが満杯であるために、バウンスメッセージを送信しました。メールボックスが満杯でなくなった場合、将来、同じ受取人に送信できる可能性があります。 |
|  | MessageTooLarge | 受取人の E メールプロバイダーは、受信したメッセージが大きすぎるために、バウンスメッセージを送信しました。メッセージのサイズを小さくすることで、同じ受取人にメッセージを送信できる可能性があります。 |
|  | ContentRejected | 受取人の E メールプロバイダーは、受信したメッセージにプロバイダーが許可しないコンテンツが含まれていたために、バウンスメッセージを送信しました。メッセージのコンテンツを変更することで、同じ受取人にメッセージを送信できる可能性があります。 |
|  | AttachmentRejected | 受取人の E メールプロバイダーは、メッセージ内に許容されないコンテンツが含まれていたために、バウンスメッセージを送信しました。たとえば、一部の E メールプロバイダーは特定のファイルタイプのファイルが添付されたメッセージや、非常に大きなファイルが添付されたメッセージを拒否する場合があります。添付ファイルのコンテンツを削除または変更することで、同じ受取人にメッセージを送信できる可能性があります。


:::note warn
ソフトバウンス＝一時的なバウンスだから何も対応しなくていい、って思ってましたが`存在しないドメイン`や`SESが把握していないメッセージが返却された場合`などでもソフトバウンスとして判断されることがあるそうです。
よって、基本的にソフトバウンスだとしても詳細を確認して、適宜対応する必要がありそうです。

参考：
[Amazon SES で存在しない宛先にメール送信した際のバウンス通知の内容を確認してみた](https://dev.classmethod.jp/articles/amazon-ses-bounce-sns-notification/#note-1149668-1
)
[AWS SESでハードバウンスのようなメールがソフトバウンス扱いで届いた場合のしくみと対処について](https://tech.connehito.com/entry/2022/09/22/180521
)
:::


メールが送信できないとかなり困ると思うので、対策について説明していきます。

### バウンス・苦情が発生した際の通知

バウンス・苦情が発生した際に通知する手段として、以下の3つがあります。
- Eメールのフィードバック転送
- フィードバック通知 (SNS)
- 設定セットのイベント送信先

細かい違いは置いといて、送られる内容の違いとしては以下のようになります。
| 内容 | Eメールのフィードバック転送  | フィードバック通知 (SNS) | 設定セットのイベント送信先 |
|:-----------|:------------:|:------------:|:------------:|
| From | 〇 | 〇 | 〇 |
| To | 〇 | 〇 | 〇 |
| 件名 | 〇 | 〇 | 〇 |
| 本文 | 〇 | × | × |
| バウンス・苦情の詳細 | × | 〇 | 〇 |

**Eメールのフィードバック転送**
Fromで指定した送信元、もしくは、メール送信時に指定したフィードバック先メールアドレスへ以下のような通知を送ります。

![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/0ebefc81-c27c-2779-4004-25bb142cfeb3.png)

![E メールのフィードバック転送.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/b18e332e-9bb6-116a-043e-b31ccf191687.png)


**フィードバック通知 (SNS)**
指定したSNSトピックに通知を送ります。
SNSトピックのサブスクリプションには、`Eメール`や`Lambda`などが指定できますが、Eメールを指定して通知を送ると以下のようになります。

![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/58d9c705-adfe-b8ff-24b9-d77a3442c58c.png)

![フィードバック通知-元のヘッダーを含めない.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/a6d79bf3-66c1-7033-b8fd-142f04153fed.png)

```json
※元のヘッダーを含めない、にしている場合はヘッダー要素がなくなります
{
	"notificationType": "Bounce",
	"bounce": {
		"feedbackId": "01060193bd9a2703-113732be-4432-47d3-962a-283878adce5d-000000",
		"bounceType": "Permanent",
		"bounceSubType": "General",
		"bouncedRecipients": [
			{
				"emailAddress": "bounce@simulator.amazonses.com",
				"action": "failed",
				"status": "5.1.1",
				"diagnosticCode": "smtp; 550 5.1.1 user unknown"
			}
		],
		"timestamp": "2024-12-13T01:20:16.000Z",
		"remoteMtaIp": "52.74.98.154",
		"reportingMTA": "dns; e234-7.smtp-out.ap-northeast-1.amazonses.com"
	},
	"mail": {
		"timestamp": "2024-12-13T01:20:15.988Z",
		"source": "***@example.com",
		"sourceArn": "arn:aws:ses:ap-northeast-1:***:identity/***@example.com",
		"sourceIp": "13.231.209.228",
		"callerIdentity": "AWSReservedSSO_AWSAdministratorAccess_4e81b62c3b93d452",
		"sendingAccountId": "***",
		"messageId": "01060193bd9a2474-8812a1a4-7447-480c-86f6-ad0c63bddab7-000000",
		"destination": [
			"bounce@simulator.amazonses.com"
		],
		"headersTruncated": false,
		"headers": [
			{
				"name": "Date",
				"value": "Fri, 13 Dec 2024 01:20:15 +0000"
			},
			{
				"name": "From",
				"value": "***@example.com"
			},
			{
				"name": "To",
				"value": "bounce@simulator.amazonses.com"
			},
			{
				"name": "Subject",
				"value": "こんにちは 中里さん"
			},
			{
				"name": "MIME-Version",
				"value": "1.0"
			},
			{
				"name": "Content-Type",
				"value": "text/plain; charset=UTF-8"
			},
			{
				"name": "Content-Transfer-Encoding",
				"value": "base64"
			},
			{
				"name": "X-Test-Id",
				"value": "1"
			}
		],
		"commonHeaders": {
			"from": [
				"***@example.com"
			],
			"date": "Fri, 13 Dec 2024 01:20:15 +0000",
			"to": [
				"bounce@simulator.amazonses.com"
			],
			"subject": "こんにちは 中里さん"
		}
	}
}
```

**設定セットのイベント送信先**
`設定セットと呼ばれるルールのグループ`にイベント送信先を設定することで、通知を行うことが出来ます。
設定セットは、以下のどちらかで設定できますが、検証済みのIDに設定することが多いと思います。

- 検証済みのID(ドメイン or メールアドレス)に設定
- メール送信時に設定

![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/3b2e168d-3393-fddf-5630-79611db540c7.png)

以下のようなjsonがイベント送信先に送られます。
```json
{
	"eventType": "Bounce",
	"bounce": {
		"feedbackId": "01060193bdbffb9f-95464acf-0ea7-49b3-b8ad-9f9f3770c97f-000000",
		"bounceType": "Permanent",
		"bounceSubType": "General",
		"bouncedRecipients": [
			{
				"emailAddress": "bounce@simulator.amazonses.com",
				"action": "failed",
				"status": "5.1.1",
				"diagnosticCode": "smtp; 550 5.1.1 user unknown"
			}
		],
		"timestamp": "2024-12-13T02:01:35.975Z",
		"remoteMtaIp": "13.213.169.222",
		"reportingMTA": "dns; e234-8.smtp-out.ap-northeast-1.amazonses.com"
	},
	"mail": {
		"timestamp": "2024-12-13T02:01:35.194Z",
		"source": "***@example.com",
		"sourceArn": "arn:aws:ses:ap-northeast-1:***:identity/***@example.com",
		"sendingAccountId": "***",
		"messageId": "01060193bdbff8da-e85c856f-738f-4755-8650-937618836252-000000",
		"destination": [
			"bounce@simulator.amazonses.com"
		],
		"headersTruncated": false,
		"headers": [
			{
				"name": "Return-Path",
				"value": "***@yahoo.co.jp"
			},
			{
				"name": "Date",
				"value": "Fri, 13 Dec 2024 02:01:35 +0000"
			},
			{
				"name": "From",
				"value": "***@example.com"
			},
			{
				"name": "To",
				"value": "bounce@simulator.amazonses.com"
			},
			{
				"name": "Subject",
				"value": "こんにちは 中里さん"
			},
			{
				"name": "MIME-Version",
				"value": "1.0"
			},
			{
				"name": "Content-Type",
				"value": "text/plain; charset=UTF-8"
			},
			{
				"name": "Content-Transfer-Encoding",
				"value": "base64"
			},
			{
				"name": "X-Test-Id",
				"value": "1"
			}
		],
		"commonHeaders": {
			"returnPath": "***@yahoo.co.jp",
			"from": [
				"***@example.com"
			],
			"date": "Fri, 13 Dec 2024 02:01:35 +0000",
			"to": [
				"bounce@simulator.amazonses.com"
			],
			"messageId": "01060193bdbff8da-e85c856f-738f-4755-8650-937618836252-000000",
			"subject": "こんにちは 中里さん"
		},
		"tags": {
			"ses:source-tls-version": [
				"TLSv1.3"
			],
			"ses:operation": [
				"SendTemplatedEmail"
			],
			"ses:configuration-set": [
				"bounce-test-set"
			],
			"ses:source-ip": [
				"13.231.209.228"
			],
			"ses:from-domain": [
				"yahoo.co.jp"
			],
			"ses:caller-identity": [
				"AWSReservedSSO_AWSAdministratorAccess_4e81b62c3b93d452"
			]
		}
	}
}
```



### サプレッションリスト

バウンス・苦情の理由によっては、メールアドレスが正しいか確認して修正した利する必要があります。
その間は、バウンス率が上がるのを防ぐためにユーザへの送信を停止したいところですが、これを自前で構築しようとすると色々考慮が必要になりそうです...が、
AWSでは`サプレッションリスト(抑制リスト)`という機能が提供されています:smile: 

サプレッションリストには以下の2つあるのですが、以下のような挙動になります。
- グローバルサプレッションリスト
- アカウントレベルのサプレッションリスト

**グローバルサプレッションリスト**
`全アカウントで共有される`リストになります。
基本的にアカウントレベルのサプレッションリストを有効化することになると思うので、あまり意識はしなくていいとは思います。

- `いずれかのAWSアカウントで`発生したハードバウンスのメールアドレスが追加されます
- このリストにあるメールアドレスへ送信を行おうとして、アカウントレベルのサプレッションリストが`無効`だと、SESは受け付けますが`bounceSubType:Suppressed`が発生します
- このリストは閲覧・編集することが出来ません
- `一定時間経過`すると、自動的にリストから削除されます

**アカウントレベルのサプレッションリスト**
`対象のアカウント`で共有されるリストになります。
デフォルトで`有効`になっています。

- `対象のAWSアカウント`で発生した`ハードバウンス`のメールアドレスが追加され、バウンス率がカウントされます
- このリストにあるメールアドレスへ送信を行おうとすると、SESは受け付けますが`bounceSubType:OnAccountSuppressionList`が発生します
- このリストにある状態で起こったバウンスは、バウンス率に`カウントされません`
- 対象のメールアドレスがグローバルサプレッションリストにあったとしても、アカウントレベルのサプレッションが`有効でリストになければ`、メールは送信できます

## テスト
SESのメール送信テスト周りについて説明します。

### ローカルでのメール送信テストについて	
ローカルでのテストで困るのは、メール実行環境の用意だと思います。
サポートにも問合せましたがAWS推奨の方法はないようなので、以下の3つから選択するのが良いと思います。

１. SESを模倣したスタブを自前で作成する
２. localstackなどのサードパーティーのエミュレーターサービスを利用する
３. 実際にSESにリクエストする ※その際は、サンドボックス環境で行うのが良い
	
### AWS環境での大量送信テストについて	
大量送信テストで困るのは、メールアドレスの用意だと思います。
サポートにも問合せしましたがAWS推奨の方法はないようなので、以下のどちらかを利用するのが良いと思います。
	
１. プラス記号を利用したメールアドレスを利用する
例：`user+001@example.com, user+002@example.com`

:::note warn
プラス記号を使ったメール送信は、ご利用のメールクライアントの送信制限に引っかかり正常に配信できない可能性があります。	
:::
	
２. AWSで用意されている[シミュレータメールアドレス](https://docs.aws.amazon.com/ja_jp/ses/latest/dg/send-an-email-from-console.html	
)を利用する	

こちらに送信したメールは実際にメールが送られるわけではないので、メールの内容確認は出来ません。
しかし、1日当たりの最大送信数のクォータの影響を受けなかったり、バウンスレートに計上されなかったりして負荷テストとしては有用です。

※バウンス時のイベント送信ログや通知などは行われます。
※1秒あたりの最大送信レートの影響は受けるので、BulkEmailEntryResultsでACCOUNT_THROTTLEDが返却されることの確認テストに利用できます


# おわりに
今回はSESを利用したメール一括送信についてまとめました。
AWSでメール送信を利用する際は、必要な知識になってくると思うのでご参考になれば幸いです:bow: