Amazon SES による Amazon SNS へのバウンス通知をサブスクリプションフィルターポリシーでフィルタリングしてみた

Amazon SES によるメール送信でバウンスが発生した場合、Amazon SNS トピックへ通知できます。Amazon SNS トピックのサブスクリプションにはフィルタリングポリシーを設定できるため、通知内容をフィルタリングしてみました。

コンバンハ、千葉(幸)です。

Amazon SES によるメール送信時にバウンスが発生した場合、その内容を Amazon SNS トピックに通知できます。

SNS トピックにはメールアドレスなどのエンドポイントをサブスクライブでき、サブスクライブ先にメッセージを送信するかどうかをサブスクリプションフィルターでフィルタリングできます。

バウンスの内容によって SNS トピックによる通知の有無をコントロールしたい機会がありましたので、実際に試してみました。

今回やりたいこと

以下のイメージです。

  • Amazon SES によるテストメール送信(Amazon SES メールボックスシミュレーター)で意図的にバウンスを発生させる
  • バウンスのタイプはPermanentとし、以下の2種類のバウンスサブタイプを発生させる
    • General
    • OnAccountSuppressionList
  • 送信に使用するアイデンティティではフィードバックメール転送を有効にし、どちらのバウンスでも E メール転送を行う
  • 送信に使用するアイデンティティではBounceタイプでの SNS トピックへのフィードバック通知を有効にする
  • 通知先の SNS トピックには E メールエンドポイントをサブスクライブし、サブスクリプションフィルターを設定する
  • サブスクリプションフィルターを変更しながら通知の有無を確認する

テストメール送信って何?フィードバックメール転送/フィードバック通知って何?といった場合は、以下のエントリにて補足説明しておりますのであわせてご参照ください。

Amazon SES テストメール送信で意図的にバウンスを発生させる

今回は以下の宛先にテストメール送信をすることでバウンスを発生させます。

# 宛先 bounceType bounceSubType
1 ドメインは存在するがユーザーが存在しないメールアドレス Permanent General
2 サプレッションリストに登録されたメールアドレス Permanent OnAccountSuppressionList

#1 については、宛先のドメインによってはbounceSubTypeGeneralになるとは限りません。その場合は Amazon SES メールボックスシミュレーターのシナリオで「バウンス」を選択するとよいでしょう。

#2 については、以下の画面のサプレッションリストに追加されたメールアドレスに送信することで発生させます。

Amazon_SES_suppression_lists

サプレッションリストには 3 つのタイプがあり、上記の画面はアカウントレベルのものを表します。

タイプ 管理主体 範囲
グローバルサプレッションリスト Amazon SES グローバル
アカウントレベルのサプレッションリスト カスタマー AWS アカウント
設定セットレベルのサプレッション カスタマー 送信単位

下に行くほど上位のタイプを上書きします。

参考:Amazon Simple Email Service でのリストとサブスクリプションの管理 - Amazon Simple Email Service

今回は一度 #1相当の操作を行うことで、宛先のメールアドレスをアカウントレベルのサプレッションリストに自動的に追加させました。手動でメールアドレスを指定して追加することもできます。

フィードバック通知コンテンツにおけるバウンスタイプ

Amazon SES のフィードバック通知により Amazon SNS に送信されるコンテンツの内訳は以下より確認できます。

特に、バウンスタイプについては以下のようにまとめられています。(ドキュメントの記述を一部省略して表現)

bounceType bounceSubType 説明
Undetermined Undetermined Amazon SES がバウンスの理由を判断できるだけの十分な情報が含まれていない。
Permanent General 受取人の E メールプロバイダーがハードバウンスの原因を示していない。
Permanent NoEmail E メールアドレスが存在しない。
Permanent Suppressed 最近の履歴でハードバウンスを生じているため、Amazon SES サプレッションリストに追加されている。
Permanent OnAccountSuppressionList アカウントレベルのサプレッションリストにあるので、このアドレスへの送信を抑制した。
Transient General 受取人の E メールプロバイダーは一般的なバウンスメッセージを送信した。
Transient MailboxFull 受取人の受信トレイが満杯である。
Transient MessageTooLarge 受信したメッセージが大きすぎる。
Transient ContentRejected 受取人のプロバイダーが許可しないコンテンツが含まれていたた。
Transient AttachmentRejected 受取人プロバイダーが許容しない添付が含まれていた。

フィードバック通知のイベント例

実際にバウンスを発生させた際に Amazon SNS に通知されるイベントの中身の例を示します。

#1,#2 いずれの場合でも、以下のアドレスで例示します。

  • 送信元アドレス:source@example.co.jp
  • 宛先アドレス:dummy@example.com

#1 ドメインは存在するがユーザーが存在しないメールアドレス宛の場合

{
  "notificationType": "Bounce",
  "bounce": {
    "feedbackId": "0106018adba7c81e-f2f2b895-154d-484e-9f3f-315d25f8e47a-000000",
    "bounceType": "Permanent",
    "bounceSubType": "General",
    "bouncedRecipients": [
      {
        "emailAddress": "dummy@example.com",
        "action": "failed",
        "status": "5.1.1",
        "diagnosticCode": "smtp; 550-5.1.1 The email account that you tried to reach does not exist. Please try\r\n 550-5.1.1 double-checking the recipient's email address for typos or\r\n 550-5.1.1 unnecessary spaces. Learn more at\r\n 550 5.1.1  https://support.google.com/mail/?p=NoSuchUser fm12-20020a0566382b0c00b00439ca012a0bsi1271622jab.6 - gsmtp"
      }
    ],
    "timestamp": "2023-09-28T11:58:40.000Z",
    "reportingMTA": "dns; googlemail.com"
  },
  "mail": {
    "timestamp": "2023-09-28T11:58:33.733Z",
    "source": "source@example.co.jp",
    "sourceArn": "arn:aws:ses:ap-northeast-1:000000000000:identity/source@example.co.jp",
    "sourceIp": "xx.xx.xx.xx",
    "callerIdentity": "cm-chiba.yukihiro",
    "sendingAccountId": "000000000000",
    "messageId": "0106018adba7ad05-442a1b86-342a-4ad2-afa8-1bb5559ca573-000000",
    "destination": [
      "dummy@example.com"
    ]
  }
}

#2 サプレッションリストに登録されたメールアドレス宛の場合

{
  "notificationType": "Bounce",
  "bounce": {
    "feedbackId": "0106018adad9c5ba-69a5c1ac-4a4a-4b63-9025-b566f294eef5-000000",
    "bounceType": "Permanent",
    "bounceSubType": "OnAccountSuppressionList",
    "bouncedRecipients": [
      {
        "emailAddress": "dummy@example.com",
        "action": "failed",
        "status": "5.1.1",
        "diagnosticCode": "Amazon SES did not send the message to this address because it is on the suppression list for your account. For more information about removing addresses from the suppression list, see the Amazon SES Developer Guide at https://docs.aws.amazon.com/ses/latest/DeveloperGuide/sending-email-suppression-list.html"
      }
    ],
    "timestamp": "2023-09-28T08:13:39.000Z",
    "reportingMTA": "dns; amazonses.com"
  },
  "mail": {
    "timestamp": "2023-09-28T08:13:39.355Z",
    "source": "source@example.co.jp",
    "sourceArn": "arn:aws:ses:ap-northeast-1:000000000000:identity/source@example.co.jp",
    "sourceIp": "xx.xx.xx.xx",
    "callerIdentity": "cm-chiba.yukihiro",
    "sendingAccountId": "000000000000",
    "messageId": "0106018adad9c49b-b5d4adf9-124f-4eb7-8b86-577dbeb66f99-000000",
    "destination": [
      "dummy@example.com"
    ]
  }
}

参考:シミュレートの場合

{
  "notificationType": "Bounce",
  "bounce": {
    "feedbackId": "0106018acc0eca6c-8485ea19-6287-4d21-8b4b-4827f401342e-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": "2023-09-25T11:17:16.000Z",
    "remoteMtaIp": "xx.xx.xx.xx",
    "reportingMTA": "dns; e234-4.smtp-out.ap-northeast-1.amazonses.com"
  },
  "mail": {
    "timestamp": "2023-09-25T11:17:14.991Z",
    "source": "source@example.co.jp",
    "sourceArn": "arn:aws:ses:ap-northeast-1:000000000000:identity/source@example.co.jp",
    "sourceIp": "xx.xx.xx.xx",
    "callerIdentity": "cm-chiba.yukihiro",
    "sendingAccountId": "000000000000",
    "messageId": "0106018acc0ec66f-4368cf8d-8f73-4fe7-8435-e969e24b5e12-000000",
    "destination": [
      "bounce@simulator.amazonses.com"
    ]
  }
}

SNS サブスクリプションフィルターを設定する

今回メール送信に使用したアイデンティティでは、フィードバック通知先としてbounce-topicという SNS トピックを指定しています。

Amazon_SES_Bounce-5813475

bounce-topicにはすでに特定のメールアドレスをサブスクライブ済みです。ここからサブスクリプションフィルターポリシーを設定してみます。

SNS_Subscripotion_filter

サブスクリプションフィルターポリシーについては以下を参照してください。

編集画面で設定をしていきます。サブスクリプションフィルターポリシーのスコープには「メッセージ属性」と「メッセージ本文」の2種類があります。Amazon SES のフィードバック通知においては「メッセージ属性」が存在しないため、「メッセージ本文」を選択する必要があります。

Subscription_Filter_Policy

bounceType: Permanent かつbounceSubType: General」の場合のみ許可、としたい場合には以下のように書きます。

{
  "bounce.bounceType": ["Permanent"],
  "bounce.bounceSubType": ["General"]
}

設定が保存されるとこのように確認できます。(ポリシーに改行が自動的に含まれていますね。)

SNS_Subscription_Filter_Policy

ちなみに:ポリシーの書き方のパターン

以下は同じ設定内容を表します。

{
  "bounce.bounceType": ["Permanent"],
  "bounce.bounceSubType": ["General"]
}
{
  "bounce":{
    "bounceType": ["Permanent"],
    "bounceSubType" : ["General"]
  }
}

どちらで書いてもポリシーとして有効です。 後者の方が自然な気がしますが、今回は前者の書き方で統一しています。(個人的な好みです。)

バウンスを発生させてみた

実際にメール送信を行いバウンスを発生させてみます。まとめるほどではないですが、以下の結果となりました。

# bounceType bounceSubType フィードバックメール転送 SNS フィードバック通知
1 Permanent General 届く 届く
2 Permanent OnAccountSuppressionList 届く 届かない

フィードバック転送によるメールはサブスクリプションフィルターと関係ないので、以下のような形式で毎回届きます。

Delivery_Status_Notification__Failure_

SNS フィードバック通知によるメール送信は意図通りフィルタリングされていました。

フィルタリングポリシー変更例1

フィルタリングポリシーを以下のように変更し、bounceTypeのみを評価するようにしてみました。

{
  "bounce.bounceType": ["Permanent"]
}

結果はこのようになります。

# bounceType bounceSubType フィードバックメール転送 SNS フィードバック通知
1 Permanent General 届く 届く
2 Permanent OnAccountSuppressionList 届く 届く

フィルタリングポリシー変更例2

以下のように変更し、「bounceType: Permanent かつbounceSubTypeが3つのうちのどれか」という条件にしてみました。

{
  "bounce.bounceType": [
    "Permanent"
  ],
  "bounce.bounceSubType": [
    "General",
    "OnAccountSuppressionList",
    "NoEmail"
  ]
}

結果は以下の通りです。

# bounceType bounceSubType フィードバックメール転送 SNS フィードバック通知
1 Permanent General 届く 届く
2 Permanent OnAccountSuppressionList 届く 届く

フィルタリングポリシー変更例3

anything-butキーワードを使用するパターンを試してみました。「bounceType: Permanent かつbounceSubTypeが2つ以外のどれか」という条件です。

{
  "bounce.bounceType": [
    "Permanent"
  ],
  "bounce.bounceSubType": [
    {
      "anything-but": [
        "Suppressed",
        "OnAccountSuppressionList"
      ]
    }
  ]
}

結果は以下の通りです。

# bounceType bounceSubType フィードバックメール転送 SNS フィードバック通知
1 Permanent General 届く 届く
2 Permanent OnAccountSuppressionList 届く 届かない

フィルタリングポリシー変更例4

最後に少し変わり種で、prefixキーワードを用いたパターンを試してみました。diagnosticCodeAmazonから始まる場合は許可、という書き方にしたかったです。

{
  "bounce.diagnosticCode": [
    {
      "prefix": "Amazon"
    }
  ]
}

結果としては意図したフィルタリングになりませんでした。イベント内のdiagnosticCodeにスペースが含まれているからかと思います。

# diagnosticCode フィードバックメール転送 SNS フィードバック通知
1 smtp; 550-5.1.1 The email account that you tried ...(略) 届く 届かない
2 Amazon SES did not send the message to this address ...(略) 届く 届かない

例えば以下のような指定であれば意図通りの結果となりました。

{
  "bounce.bounceType": [
    "Permanent"
  ],
  "bounce.bounceSubType": [
    {
      "prefix": "On"
    }
  ]
}
# bounceType bounceSubType フィードバックメール転送 SNS フィードバック通知
1 Permanent General 届く 届かない
2 Permanent OnAccountSuppressionList 届く 届く

終わりに

Amazon SES によるバウンスのフィードバック通知を、Amazon SNS のサブスクリプションフィルターでフィルタリングしてみました。

イベントの中身がある程度はっきりしている場合には、こういった方法でフィルタリングすることで通知量が減らせ、本来検知したい内容をよりわかりやすく把握できるようになります。あるいは、サブスクリプションを複数作成することで、内容によって通知先を振り分けるということも実現できます。

ただ、例えばバウンスサブタイプNoEmailが返ってくることを期待してフィルタリングを設定したが実際の環境ではGeneralが返ってくる、といったケースもあるかもしれません。(バウンスメッセージは受信側のプロバイダーに依存する部分もあるため。)

決め打ちで指定せず、ある程度パターンが見えてからフィルタリングすることをおすすめします。

以上、 チバユキ (@batchicchi) がお送りしました。

参考