【小ネタ】Amazon SES のメール受信で S3 バケットに格納してみる

記事タイトルとURLをコピーする

みなさん、こんにちは。 AWS CLI が好きなテクニカルサポート課の市野です。
そういえば、2024年9月から正式には「テクニカルサポート1課」の所属となっていて、響き・ニュアンス的に 捜査一課 っぽくて気に入っています。

さて、先日、恒久的にではなくほんの一瞬だけ独自ドメインのメールを受信したい要件があり、Amazon SES の受信メール機能を試した機会がありましたので、まとめてみます。

(余談)ちなみに、ほんの一瞬だけ独自ドメインのメールを受信したかった理由は、AWS Certificate Manager での E メール検証による証明書発行を検証したかった用途でした。

クリックで目次が表示されます。

Amazon SES のメール受信機能とは

docs.aws.amazon.com

詳細は、上記 AWS 公式ドキュメントをご確認いただきたいですが、おおまかに以下のような機能となります。

できること

  • 以下のような受信ルールによる高度な処理(アクション)の実施
    「受信ルールセット」内に単一、あるいは複数の受信ルールを設置し、受信ルールの評価による処理を実行することが可能です。
    • ヘッダーの追加:
      受信したメールにカスタムのヘッダーを追加する機能ですが、単体の利用ではなく、他のアクションと組み合わせて利用するものとされています。
    • バウンス応答を返す:
      SMTP 応答コードやSMTP ステータス コード、メッセージなどを含めたバウンス応答を返すことが可能です。
    • Lambda 関数を呼び出す:
      別途定義している Lambda 関数を指定して後続処理に繋げることが可能です。
    • S3 バケットに配信:
      受信したメールメッセージを指定の S3 バケットに格納することが可能です。
    • Amazon SNS トピックに公開:
      別途定義している Amazon SNS トピックにメールを公開(リレーする感覚に近い?)します。
      制限として、メールヘッダーを含めたサイズが 150 KB までとなっているため、このサイズを超えるメール受信の場合は、S3 バケットへの配信をする必要があります。
    • ルール セットの停止:
      受信ルールセットの評価を終了させます。
    • Amazon WorkMail との統合:
      Amazon SES と同一リージョンに別途作成されている Amazon WorkMail と統合することが可能です。
      ただし執筆時点で Amazon WorkMail が欧州 (アイルランド)、米国東部 (バージニア北部)、米国西部 (オレゴン) でしか利用できないため、東京リージョンや大阪リージョンで作成する Amazon SES の受信ルールでは統合ができないこととなります。
  • IP アドレスフィルター
    特定の IP アドレスまたは IP アドレス範囲から差し出されたメールに対し、明示的なブロックあるいは許可ができます

できないこと

  • メールクライアントを用いた受信メールの受け取り
    POP や IMAP サーバーが存在しないため Outlook などのメールクライアントを用いた受信はできず、そのようなユースケースの場合は Amazon WorkMail の利用検討が推奨されています。

利用の前提

Email Receiving endpoints に記載されている、受信用のエンドポイントが用意されているリージョンでのみ利用が可能となります。
なお、本エントリ執筆時点で東京リージョン(ap-northeast-1)、大阪リージョン(ap-northeast-3)ともに利用が可能です。

処理の流れ(概要)

メールが S3 バケットへ転送される流れ(概略)

図にしてみると上記のようなものとなるかと思いますが、「Amazon SES 受信ルールセット」という器が先にあり、その中に単一あるいは複数の受信ルールを含めることができるようになる仕組みです。

そのため今回は、Amazon SES 受信ルールセット=ドメイン、受信ルール=ローカルパート、の感覚で管理する想定で作成しています。

やってみる

手順の概要

  1. S3 バケットの作成
  2. アクセス権の作成
    以下、いずれかの方法で Amazon SES に届いたメールを S3 バケットへ格納するための権限を設定する
    • パターン A:IAM ロールによる制御
    • パターン B:S3 バケットポリシーによる制御
  3. Amazon SES での ID の作成
  4. 受信用の MX レコードの設定
  5. 受信ルールの設定

0. 定数・変数の設定

AWS_REGION="ap-northeast-1"
DOMAIN="example.com"
SUB_DOMAIN="sub."
FQDN="${SUB_DOMAIN}${DOMAIN}"
RULE_SET_NAME=${FQDN}
RECEIPT_RULE_NAME="admin"
S3_BUCKET_NAME="${RECEIPT_RULE_NAME}".at."${FQDN}"
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)

1. S3 バケットの作成

事前に定義した定数、変数の値に基づいて、受信メールを保存する S3 バケットを作成します。

aws s3 mb s3://"${S3_BUCKET_NAME}"

2. アクセス権の作成

公式ドキュメントによると、IAM ロールによる実行の場合は、SES メール受信が利用できない AWS リージョンにある S3 バケットを利用する場合にこの手法を取る必要がある、と記載があります。

パターン A:IAM ロールによる制御

環境準備をささっとやってしまいたかったものの、AWS CLI が好きな私としては手順自体は CLI として残したいと考えています。

AWS CLI で IAM ロールを作成する場合、① IAM ポリシードキュメントの作成、② 信頼ポリシードキュメントの作成、③ IAM ロールの作成、と手数が少々多いので、実装自体はパターン B の S3 バケットポリシーで制御する方式を採用しました。

以下は、IAM ポリシーでの制御の際に最低限必要な項目です。

IAM ポリシードキュメントの作成

公式ドキュメントにもあるとおり、Amazon SNS への連携や、カスタマーマネージド KMS キーを利用する場合には、それらの権限付与も必要となります。

cat << EOF >iam-policy-document.json
{
   "Version":"2012-10-17",
   "Statement":[
        {
            "Sid": "S3Access",
            "Effect": "Allow",
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::${S3_BUCKET_NAME}/*"
        }
   ]
}
EOF
信頼ポリシードキュメントの作成
cat << EOF >iam-trust-policy-document.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowSESAssume",
            "Effect": "Allow",
            "Principal": {
                "Service": "ses.amazonaws.com"
            },
            "Action": "sts:AssumeRole",
            "Condition": {
                "StringEquals": {
                  "AWS:SourceAccount":"${AWS_ACCOUNT_ID}",
                  "AWS:SourceArn": "arn:aws:ses:${AWS_REGION}:${AWS_ACCOUNT_ID}:receipt-rule-set/${RULE_SET_NAME}:receipt-rule/${RECEIPT_RULE_NAME}"
                }
            }
        }
    ]
}
EOF
IAM ポリシーの作成
POLICY_NAME="PolicyForReceiveSEStoS3Bucket"
aws iam create-policy \
  --policy-name "${POLICY_NAME}" \
  --policy-document file://iam-policy-document.json
IAM ロールの作成
ROLE_NAME="ReceiveSEStoS3Bucket"
aws iam create-role \
  --role-name "${ROLE_NAME}" \
  --assume-role-policy-document file://iam-trust-policy-document.json
作成した IAM ロールへのポリシーのアタッチ
POLICY_ARN=$(aws iam list-policies --scope Local --no-only-attached --query "Policies[?PolicyName==\`${POLICY_NAME}\`].Arn" --output text)
aws iam attach-role-policy \
  --role-name "${ROLE_NAME}" \
  --policy-arn "${POLICY_ARN}"

パターン B:S3 バケットポリシーによる制御

バケットポリシードキュメントの作成
cat << EOF >s3bucketpolicy.json
{
  "Version":"2012-10-17",
  "Statement":[
    {
      "Sid":"AllowSESPuts",
      "Effect":"Allow",
      "Principal":{
        "Service":"ses.amazonaws.com"
      },
      "Action":"s3:PutObject",
      "Resource":"arn:aws:s3:::${S3_BUCKET_NAME}/*",
      "Condition":{
        "StringEquals":{
          "AWS:SourceAccount":"${AWS_ACCOUNT_ID}",
          "AWS:SourceArn": "arn:aws:ses:${AWS_REGION}:${AWS_ACCOUNT_ID}:receipt-rule-set/${RULE_SET_NAME}:receipt-rule/${RECEIPT_RULE_NAME}"
        }
      }
    }
  ]
}
EOF
バケットポリシーの付与
aws s3api put-bucket-policy \
  --bucket "${S3_BUCKET_NAME}" \
  --policy file://s3bucketpolicy.json

3. Amazon SES での ID の作成

aws ses verify-domain-identity \
  --domain "${FQDN}"

ses verify-domain-identity サブコマンドの実行後、以下のようなトークンが返却されます。 検証のために、このトークンの値を "${DOMAIN}" の TXT レコードとして登録する必要があります。

{
    "VerificationToken": "2D+xxxxxxxx+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxXk="
}

もう一度トークンの値を取得します。
また、私の環境では、DNS の管理に Route 53 を利用している環境だったため、TXT レコードの投入まで続けてやっていきます。

変数の設定

VERIFICATION_TOKEN=$(aws ses get-identity-verification-attributes --identities "${FQDN}" --output text | awk '{print $3}')
HOSTED_ZONE_ID=$(aws route53 list-hosted-zones --query "HostedZones[?contains(Name,\`${DOMAIN}\`)].Id" --output text | awk -F '/' '{print $3}')

レコード定義用ファイルの作成

Amazon SES ID 定義用の TXT レコード設定用に change-resource-record-sets-for-txt.json として JSON ファイルを作成します。

cat << EOF >change-resource-record-sets-for-txt.json
{
  "Comment": "Create TXT Record for Amazon SES domain verification",
  "Changes": [
    {
      "Action": "CREATE",
      "ResourceRecordSet": {
        "Name": "${FQDN}",
        "Type": "TXT",
        "TTL": 300,
        "ResourceRecords": [
          {
            "Value": "\"${VERIFICATION_TOKEN}\""
          }
        ]
      }
    }
  ]
}
EOF

DNS レコードの作成

aws route53 change-resource-record-sets \
  --hosted-zone-id "${HOSTED_ZONE_ID}" \
  --change-batch file://change-resource-record-sets-for-txt.json

上記サブコマンド実行後、/change/〜 のような形式の Id を伴う ChangeInfo が返却されていればリクエストが通っているので ses wait identity-exists サブコマンドを利用して、Amazon SES での ID 作成が検証済みとなるまで待機をする。

aws ses wait identity-exists \
  --identities "${FQDN}"

上記コマンド実行後、CloudShell のプロンプトが再び入力待ち状態に復帰後、後続の作業を続ける。

4. 受信用の MX レコードの設定

前述の Email Receiving endpoints に記載されている、受信用のエンドポイントを MX レコードとして登録します。
なお、該当のリージョン別の受信用のエンドポイントを AWS CLI で取得する方法がないため、一旦は、Email Receiving endpoints の表から直接転記することとします。

以下の例は、東京リージョン(ap-northeast-1)で登録するべく、inbound-smtp.ap-northeast-1.amazonaws.com の値を取得している前提となります。
また、MX レコードの優先度を決め打ちで 10 としていますが、お使いの環境やご要件に合わせて適宜変更ください。

DNS レコードの定義

EMAIL_RECEIVING_ENDPOINT="inbound-smtp.ap-northeast-1.amazonaws.com"
PRIORITY="10"

レコード定義用ファイルの作成

メール時受信用の MX レコード設定用に change-resource-record-sets-for-mx.json として JSON ファイルを作成します。

cat << EOF >change-resource-record-sets-for-mx.json
{
  "Comment": "Create MX Record for Amazon SES Receiving",
  "Changes": [
    {
      "Action": "CREATE",
      "ResourceRecordSet": {
        "Name": "${FQDN}",
        "Type": "MX",
        "TTL": 300,
        "ResourceRecords": [
          {
            "Value": "${PRIORITY} ${EMAIL_RECEIVING_ENDPOINT}"
          }
        ]
      }
    }
  ]
}
EOF

DNS レコードの作成

aws route53 change-resource-record-sets \
  --hosted-zone-id "${HOSTED_ZONE_ID}" \
  --change-batch file://change-resource-record-sets-for-mx.json

5. 受信ルールの設定

Amazon SES 受信ルールセットを作成します。

aws ses create-receipt-rule-set \
  --rule-set-name "${RULE_SET_NAME}"

上記サブコマンド実行後、正しくリクエストができていた場合、値の返却はありません。

受信ルールの作成

ルール設定用 JSON ファイルの作成

今回は S3 バケットに格納できれば良いので、最低限の設定項目のみ定義し、受信ルール設定用の JSON ファイルを作成します。

cat << EOF >receipt-rule.json
{
    "Name": "${RECEIPT_RULE_NAME}",
    "Enabled": true,
    "TlsPolicy": "Optional",
    "Recipients": [
        "${RECEIPT_RULE_NAME}@${FQDN}"
    ],
    "Actions": [
        {
            "S3Action": {
                "BucketName": "${S3_BUCKET_NAME}"
              }
        }
    ],
    "ScanEnabled": true
}
EOF

なお、S3 バケットへの配信をする場合のすべてのオプション項目は、公式ドキュメントをご参照ください。

docs.aws.amazon.com

受信ルールの作成
aws ses create-receipt-rule \
  --rule-set-name "${RULE_SET_NAME}" \
  --rule file://receipt-rule.json

上記コマンドが正常終了した場合、値の返却などは行われませんが、Actions の S3Action に設定した S3 バケットに対し、AMAZON_SES_SETUP_NOTIFICATION というファイルが生成されます。

AMAZON_SES_SETUP_NOTIFICATION の内容

おおむね以下のような「S3 バケットに対して Amazon SES のメールを転送するようにしたので、このファイルが配置されたよ」という趣旨が書かれたファイルとなります。

cat AMAZON_SES_SETUP_NOTIFICATION
Date: Mon, 11 Nov 2024 02:45:55 +0000
To: recipient@example.com
From: Amazon Web Services <no-reply-aws@amazon.com>
Subject: Amazon SES Setup Notification

Hello,

You received this message because you attempted to set up Amazon SES to deliver emails to this S3 bucket.

Please note that the rule that you configured to deliver emails to this S3 bucket is only valid if the entire setup process is successful. For more information about
setting up email-receiving rules, see the Amazon SES Developer Guide at http://docs.aws.amazon.com/ses/latest/DeveloperGuide/Welcome.html .

Thank you for using Amazon SES!

The Amazon SES Team
Amazon SES 受信ルールセットの有効化

最後に、Amazon SES 受信ルールセット自体を有効化して、転送を開始する必要があります。

aws ses set-active-receipt-rule-set \
  --rule-set-name "${RULE_SET_NAME}"

こちらのコマンドも、実行後正常にリクエストが通った場合、何の値も返却されませんが、状態を確認したい場合は以下のように実行して確認が可能です。

aws ses describe-active-receipt-rule-set
得られる出力例

describe-active-receipt-rule-set サブコマンドを実行することで、有効となっている Amazon SES 受信ルールセットがあれば返却され、有効となっている Amazon SES 受信ルールセットが一つもなければ、何も出力されない仕様となっているようです。

{
    "Metadata": {
        "Name": "sub.example.com",
        "CreatedTimestamp": "2024-11-11T02:45:28.904000+00:00"
    },
    "Rules": [
        {
            "Name": "admin",
            "Enabled": true,
            "TlsPolicy": "Optional",
            "Recipients": [
                "admin@sub.example.com"
            ],
            "Actions": [
                {
                    "S3Action": {
                        "BucketName": "admin.at.sub.example.com"
                    }
                }
            ],
            "ScanEnabled": true
        }
    ]
}

処理の完了

これで、受信ルールに Recipients として設定したメールアドレスに対して届くメールが、指定した S3 バケットに転送されることとなります。

今回、私が一時的にメールを受信したかった理由は AWS Certificate Manager での E メール検証による SSL/TLS の発行でしたので、これで検証用のメールを受け取ることができるようになりました。

まとめ

私の好みで AWS CLI の手順として書いているため、一つ一つの手順として分解される形となるため煩雑なように見えますが、簡単にメールを受信する仕組みを作れるかと思います。

ちゃんとメールを運用するのはなかなか骨が折れるものだと思いますので、送信だけでなく、受信についても Amazon SES のマネージドに寄りかかれるのは便利だと感じました。

また、今回はシンプルな目的だったため、S3 バケットのバケットポリシー側でアクセス権の設定をしましたが、よりきめ細かいアクセス制御を行いたい場合は、IAM ロールによる制御が良いと思われます。

冒頭でも記載していますが、Outlook などのメールクライアントを用いた受信をしなくて良い要件であればご検討に値するのではないかと考えられます。

本記事がどなたかのお役に立てば幸いです。

ではまた。

市野 和明 (記事一覧)

マネージドサービス部・テクニカルサポート課

お客様から寄せられたご質問や技術検証を通じて得られた気づきを投稿していきます。

情シスだった前職までの経験で、UI がコロコロ変わる AWS においては GUI で手順を残していると画面構成が変わってしまって後々まごつくことが多かった経験から、極力変わりにくい AWS CLI での記事が多めです。

X(Twitter):@kazzpapa3(AWS Community Builder)