AWS SESのバウンスレートを下げる方法（緊急対応編）

## はじめに
本記事は、[Sun* Advent Calendar 2024](https://adventar.org/calendars/10035)の20日目の記事です。

こんにちは！
2024年4月から新卒としてSun*でバックエンドエンジニアとして働いているAsutoです。

現在、Sun*ではモバイルヘルスケアアプリ開発のプロジェクトに参画しており、サーバーサイド（主にNestJS）を担当しています。プロジェクトでは様々なAWSサービスを活用しており、その中でSimple Email Service（SES）を使用しています。最近、SESのバウンスレートを一刻も早く下げる必要が出てきたため、その際に実施した対応について記録しておこうと思います。
## 実現すること
バウンスレートを一時的に1%近くまで下げる。

:::note info
理由に関しては「バウンスレートとは」の部分で説明します。本記事では、AWSの公式ブログを紹介していますが、プロジェクトのメール構成上（主に認証関連のメールを使用しているため）、公式ブログで紹介されている方法が適用しにくいと感じました。加えて、一刻も早くバウンスレートを下げる必要があったため、公式ブログの内容をもとに暫定的な対策を構成しました。
:::
## SESとバウンスレートについて
### AWS Simple Email Service(SES)とは
AWSが提供するメール配信サービスです。メール配信サービスとは一度に大量のメールを配信できるサービスのことで、AWS SESを導入することでメール配信だけでなく、管理画面より配信先やコンテンツの設定、登録、配信、分析などが可能です。

こちらが公式のドキュメントになります。詳細について確認したい方はこちらをご確認ください。:bow_tone1:

https://docs.aws.amazon.com/ja_jp/ses/?id=docs_gateway

### バウンスレートとは
SESを使用してメールを配信する際に、何らかの原因により正常に配信できなかったメールをバウンスメールと呼びます。そのバウンスメールの割合を示しているのがバウンスレートです。

> アカウントのバウンス率は 5% 未満に維持する必要があります。アカウントのバウンス率が 10% を超えた場合、当社はお客様のアカウントによる E メール送信機能を一時的に停止することがあります。

公式ドキュメントではこのように記載されており、バウンスメールの発生率が一定の割合を超えると、メール配信機能が停止してしまう仕様になっています。
つまり、この仕様を知らずに運用すると意図せずメール配信が止まってしまうリスクがあります。

## 実現した対策
### 参考にした記事
今回、バウンスレートを下げる方法としてはこちらの公式ブログをもとにした方法を紹介させていただきます。

https://aws.amazon.com/jp/blogs/messaging-and-targeting/never-send-to-old-addresses-but-what-if-you-have-to/

内容としては、不明なアドレス1件に対して、有効なアドレス20件以上に送信することで、全体の送信品質を維持するという方法です。

仕組みとしては、バウンスレートは総送信数に対する比率なので、メールの送信成功が増えればバウンスレートは下がります。そのため、バウンスレートが上昇してきた場合、確実に届くメールアドレスにメールを送信することで、作為的にバウンスレートを下げることが可能になります。

ただし、この対応はバウンスレートの上昇を抑制するための一時的な方法であり、根本的な解決策ではありません。根本的に解決したい場合は、以下のAWS公式のベストプラクティスをぜひ参照し、実行してみてください。

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

また今回、ローカル環境で実行することもできるのですが、AWS環境で管理および実行できるように下記の構成のもと作成しました。
### 構成
:::note info
冒頭でも説明しましたが、メール送信の構成上、不明なアドレス1件に対して、有効なアドレス20件以上に送信するということが難しい＆一気にバウンスレートを下げたかったので1時間に100通メールを送信するようにしました。
:::
- EventBridge
    - 1時間ごとにLambdaを呼び出すように設定
    - ただ、常に稼働はさせたくないのでON/OFF切り替えられるようにする
        - IaCの場合、[ignore_changes](https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#ignore_changes)を利用して特定の項目をterraformで管理しないようにする
- Lambda
    - スケジューラーからトリガーを受け取り、用意した1つの有効なメールアドレスに100通のメールを送信する[^1]
    - 合計送信メール数、送信先アドレス、送信元アドレスはAWS SSMで管理
- SES
    - Lambda経由でメール送信を行う

### Lambdaの作成
```js
const { SESClient, SendEmailCommand } = require('@aws-sdk/client-ses');

const sesClient = new SESClient({
  region: process.env.AWS_REGION,
});
const sender = process.env.SENDER; // メール送信元
const recipient = process.env.RECIPIENT; // 送信先
const counts = Number(process.env.COUNTS); // メール送信数

// メール送信関数
const sendEmail = async () => {
  const params = {
    Source: sender,
    Destination: { ToAddresses: recipient },
    Message: {
      Subject: { Data: 'SESバウンスレート対策' },
      Body: { Text: { Data: 'SESバウンスレート対策のためのメールです。' } },
    },
  };

  try {
    const command = new SendEmailCommand(params);
    const response = await sesClient.send(command);
    console.log(
      `Email sent to ${recipient}. Message ID: ${response.MessageId}`,
    );
  } catch (error) {
    console.error('Error sending email:', error);
    throw error;
  }
};

// メイン処理
export const handler = async () => {
  try {
    console.log('Start sending mail');
    for (let i = 0; i < counts; i++) {
      await sendEmail();
    }
    console.log('Completed 100 cycles');
  } catch (error) {
    console.error('Error sending email:', error);
  }
};
```
Lambdaに関しては、通常通りSES経由でメールを送信するようにしているだけなので特に変わったことはしていません。構成にも記載しましたが、EventBridgeがトリガーになるため、そちらの設定でLambdaが呼び出され、SES経由でメール送信されるような仕組みになっています。

### 実行結果
![スクリーンショット 2024-12-16 19.12.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3736321/3be86863-1197-4a60-eb1f-61c6c4225640.png)
一時は4.5%あったバウンスレートを二日間で1.3%まで低下させることができました。
ただ、バウンスレートの上昇を一時的に抑制したに過ぎないので、今後もバウンスレートはじわじわ上昇し、また同じように5%近くまでバウンスレートが上がってしまう可能性があります。
そこで、本記事で紹介したベストプラクティスにもあるように以下の対応を今後検討し実施していく必要がありそうです。
- モニタリングシステムを実装し、無効なメールアドレスを洗い出す
- バウンス率を高くしているアドレスへの電子メールの送信を停止する
- Amazon SES アカウントレベルのサプレッションリストをオンにする
    - ハードバウンス[^2]の原因となったメールアドレスがサプレッションリストに追加されます
    - 追加後、これらのアドレスにはメッセージが送信されなくなるため、バウンス率が低下する

## おわり
最後まで記事をお読みいただき、ありがとうございました！
今回、バウンスレートを下げるための対策について触れましたが、この対策をした際にNode.jsでの並列処理についても色々と調べたので後続として記事を出す予定です。
また、Sun*ではこのように新卒でもインフラ業務に携わることができるなど色々と新しいことに挑戦できる環境となっています！
明日は古川さんの「Cloud Functions を利用した非常駐型の Discord Bot を Golang で作った話」になります！
お楽しみに！

## PR
Sun*では、一緒に働く仲間を募集しています。
まずはカジュアル面談からでもお気軽にご応募ください！

▼会社情報はこちら

https://sun-asterisk.com/

▼募集要項・ご応募はこちら

https://sun-asterisk.com/recruitment/

[^1]:メールアドレスが「有効であるか」ということが重要なので送信先は複数存在しても問題ありません
[^2]:永続的なEメール配信の障害でメールボックスが存在しないなど