AWS CLIを利用してAmazon SESの設定をやってみた – メール受信編

はじめに

こんにちは、中山です。

前回は以下のエントリでAWS CLIを利用したAmazon Simple Email Service(以下SES)のメール送信環境の構築方法についてご紹介しました。

今回は北部バージニアリージョン上にSESのメール受信環境を構築する方法についてご紹介します。SESを利用したメール受信環境の構築方法については以下のエントリが参考になります。合わせてお読みいただくとより理解が深まるかと思います。

前回同様、検証に利用したAWS CLIのバージョンは執筆時点(2016/12/27)の最新バージョンである1.11.34です。バージョンによって内容が変更される可能性があります。その点ご了承ください。

やってみる

ドメインの検証についてはすでにセットアップ済みとして割愛させていただきます。設定方法については以前のエントリを参照してください。また、メール受信時のアクションとしてS3/SNS/Lambda関数を利用します。それぞれの作成方法についても本エントリの主旨と離れるので今回は割愛させていただきます。

1. MXレコードの作成

SESでメールを受信するためには該当ドメインに対してMXレコードを設定する必要があります。こちらのドキュメントにあるように、SESを利用するリージョンによってエンドポイントが変わります。今回は北部バージニアリージョンなので以下の内容でレコードセットを作成します。

Namt Type Value
<_your_verification_domain> MX 10 inbound-smtp.us-east-1.amazonaws.com

以下のコマンドでMXレコードを作成します。

# 検証済みドメインを変数に代入
$ domain=<_YOUR_VERIFICATION_DOMAIN_>
# MXレコードの作成
$ aws route53 change-resource-record-sets \
  --hosted-zone-id "$(
    aws route53 list-hosted-zones \
      --query 'HostedZones[?Name==`<_YOUR_DOMAIN_>.`].Id' \
      --output text \
    | sed -E 's@/hostedzone/@@')" \
  --change-batch "$(jo Comment="test" Changes=$(jo -a $(jo Action=UPSERT ResourceRecordSet=$(jo Name=$domain. Type=MX TTL=1800 ResourceRecords=$(jo -a $(jo Value=10_inbound-smtp.us-east-1.amazonaws.com))))) | sed -E 's/10_/10 /')"
{
    "ChangeInfo": {
        "Status": "PENDING",
        "Comment": "test",
        "SubmittedAt": "2016-12-27T11:59:14.832Z",
        "Id": "/change/C3VRI33QL90WWP"
    }
}

jo コマンドで生成されるJSONは以下の通りです。

{
   "Comment": "test",
   "Changes": [
      {
         "Action": "UPSERT",
         "ResourceRecordSet": {
            "Name": "<_YOUR_VERIFICATION_DOMAIN_>.",
            "Type": "MX",
            "TTL": 1800,
            "ResourceRecords": [
               {
                  "Value": "10 inbound-smtp.us-east-1.amazonaws.com"
               }
            ]
         }
      }
   ]
}

2. ルールセットの作成

SESではメール受信時に特定のルールで定義されたアクションを実行してくれます。それぞれのルールはルールセットと呼ぶ概念でグルーピングされ、複数のルールを適用することができます。まずは、このルールセットを作成していきます。

# ルールセットの作成
$ aws ses create-receipt-rule-set \
  --rule-set-name test-rule-set
# ルールセットが作成されたことを確認
$ aws ses list-receipt-rule-sets
{
    "RuleSets": [
        {
            "CreatedTimestamp": "2016-12-27T09:04:26.723Z",
            "Name": "test-rule-set"
        }
    ]
}
# ルールセットの内容を確認
$ aws ses describe-receipt-rule-set \
  --rule-set-name test-rule-set
{
    "Rules": [],
    "Metadata": {
        "CreatedTimestamp": "2016-12-27T09:04:26.723Z",
        "Name": "test-rule-set"
    }
}

3. ルールの作成

続いてルールセットに関連付けるルールを作成していきます。今回は各アクション毎に一つづつルールを作成するという方式で設定していきます。現時点で設定可能なアクションは以下の通りです。今回はWorkMailとルールのストップ以外のアクションを設定してみます。

  • S3
  • SNS
  • Lambda関数
  • ヘッダの追記
  • バウンス
  • WorkMail
  • ルールのストップ

また、ルール毎に以下の設定をすることが可能です。

  • ルール適用対象の受信メールアドレスの指定(ドメインでの指定も可能)
  • 受信メールがTLS接続していない場合の拒否
  • 受信メールのウイルス/スパム用スキャン
  • アクション実行時にSNSトピックへパブリッシュ(アクションによっては設定できない場合もある)

今回はTLSは無効、スキャンは有効化、そして受信メールアドレスはすでに検証済みのドメインを指定します。まだ検証していないメールアドレスまたはドメインを指定した場合は都度TXT/MXレコードの設定が必要になるのでご注意ください。SNSトピックへのパブリッシュも設定しません。

まず最初にS3用ルールを作成してみます。なおSESからS3にオブジェクトをputさせるために以下のようなバケットポリシーを該当のバケットに付与しておいてください。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowSESPuts",
      "Effect": "Allow",
      "Principal": {
        "Service": "ses.amazonaws.com"
      },
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::<_YOUR_S3_BUCKET_>/*",
      "Condition": {
        "StringEquals": {
          "aws:Referer": "<_YOUR_AWS_ACCOUNT_ID_>"
        }
      }
    }
  ]
}

以下のコマンドでルールを作成します。ちなみに、S3アクションを作成すると該当バケットに「AMAZON_SES_SETUP_NOTIFICATION」という名前のオブジェクトが作成されます。

# 受信したメールをputさせるバケット名を変数に代入
$ bucket=<_YOUR_S3_BUCKET_>
# S3用ルールの作成
$ aws ses create-receipt-rule \
  --rule-set-name test-rule-set \
  --rule "$(jo Name=s3-rule Recipients=$(jo -a $domain) Enabled=true ScanEnabled=true Actions=$(jo -a $(jo S3Action=$(jo ObjectKeyPrefix=ses BucketName=$bucket))) TlsPolicy=Optional)"
# ルールの内容を確認
$ aws ses describe-receipt-rule \
  --rule-name s3-rule \
  --rule-set-name test-rule-set
{
    "Rule": {
        "Name": "s3-rule",
        "Recipients": [
            "<_YOUR_VERIFICATION_DOMAIN_>"
        ],
        "Enabled": true,
        "ScanEnabled": true,
        "Actions": [
            {
                "S3Action": {
                    "ObjectKeyPrefix": "ses",
                    "BucketName": "<_YOUR_S3_BUCKET_>"
                }
            }
        ],
        "TlsPolicy": "Optional"
    }
}

jo コマンドで生成されるJSONファイルは以下の通りです。

{
   "Name": "s3-rule",
   "Recipients": [
      "<_YOUR_VERIFICATION_DOMAIN_>"
   ],
   "Enabled": true,
   "ScanEnabled": true,
   "Actions": [
      {
         "S3Action": {
            "ObjectKeyPrefix": "ses",
            "BucketName": "<_YOUR_S3_BUCKET_>"
         }
      }
   ],
   "TlsPolicy": "Optional"
}

続いてSNS用ルールを作成します。SNSをアクションとしたルールでは受信メールのエンコーディングを指定可能です。Base64とUTF-8を選択できるのですが、UTF-8の場合は特殊な文字が正常に表示できない場合があるようです。そのため、Base64を指定します。

# SNSトピック名を変数に代入
$ sns_topic=<_YOUR_SNS_TOPIC_>
# SNS用ルールの作成
$ aws ses create-receipt-rule \
  --rule-set-name test-rule-set \
  --rule "$(jo Name=sns-rule Recipients=$(jo -a $domain) Enabled=true ScanEnabled=true Actions=$(jo -a $(jo SNSAction=$(jo TopicArn="$(aws sns list-topics --query 'Topics[?contains(TopicArn,`'$sns_topic'`)].TopicArn' --output text)" Encoding=Base64))) TlsPolicy=Optional)"
# ルールの内容を確認
$ aws ses describe-receipt-rule \
  --rule-name sns-rule \
  --rule-set-name test-rule-set
{
    "Rule": {
        "Name": "sns-rule",
        "Recipients": [
            "<_YOUR_VERIFICATION_DOMAIN_>"
        ],
        "Enabled": true,
        "ScanEnabled": true,
        "Actions": [
            {
                "SNSAction": {
                    "TopicArn": "arn:aws:sns:us-east-1:************:<_YOUR_SNS_TOPIC_>",
                    "Encoding": "Base64"
                }
            }
        ],
        "TlsPolicy": "Optional"
    }
}

jo コマンドで生成されるJSONは以下の通りです。

{
   "Name": "sns-rule",
   "Recipients": [
      "<_YOUR_VERIFICATION_DOMAIN_>"
   ],
   "Enabled": true,
   "ScanEnabled": true,
   "Actions": [
      {
         "SNSAction": {
            "TopicArn": "arn:aws:sns:us-east-1:************:<_YOUR_SNS_TOPIC_>",
            "Encoding": "Base64"
         }
      }
   ],
   "TlsPolicy": "Optional"
}

今度はLambda関数用ルールを作成します。なお、アクションとしてLambda関数を指定する場合はSESからLambda関数をInvokeする権限が必要です。以下のようなポリシーをLambda関数に付与しておいてください。

{
  "Version": "2012-10-17",
  "Id": "default",
  "Statement": [
    {
      "Sid": "allowSesInvoke",
      "Effect": "Allow",
      "Principal": {
        "Service": "ses.amazonaws.com"
      },
      "Action": "lambda:InvokeFunction",
      "Resource": "arn:aws:lambda:us-east-1:************:function:<_YOUR_LAMBDA_FUNCTION_>",
      "Condition": {
        "StringEquals": {
          "AWS:SourceAccount": "<_YOUR_AWS_ACCOUNT_ID_>"
        }
      }
    }
  ]
}

Lambda関数の実行方法としてEventとRequestResponseが選択できます。Lambda関数を同期(RequestResponse)/非同期(Event)で呼び出すのかの指定をするようですが、基本的にEventを指定するようにと書かれているので今回はこの設定にしておきます。該当の文面を引用します。

This determines whether the Lambda function is invoked synchronously or asynchronously. We recommend that you use the Event invocation type unless synchronous execution is necessary for mail flow control. Amazon SES enforces a 30-second timeout on RequestResponse invocations, which is shorter than AWS Lambda's normal limit.An invocation type of Event means that the function will be invoked asynchronously.

以下のコマンドでルールを作成します。

# Lambda関数名を変数に代入
$ lambda_function=<_YOUR_LAMBDA_FUNCTION_>
# ルールの作成
$ aws ses create-receipt-rule \
  --rule-set-name test-rule-set \
  --rule "$(jo Name=lambda-rule Recipients=$(jo -a $domain) Enabled=true ScanEnabled=true Actions=$(jo -a $(jo LambdaAction=$(jo InvocationType=Event FunctionArn="$(aws lambda list-functions --query 'Functions[?contains(FunctionArn,`'$lambda_function'`)].FunctionArn' --output text)"))) TlsPolicy=Optional)"
# ルールの内容を確認
$ aws ses describe-receipt-rule \
  --rule-name lambda-rule \
  --rule-set-name test-rule-set
{
    "Rule": {
        "Name": "lambda-rule",
        "Recipients": [
            "<_YOUR_VERIFICATION_DOMAIN_>"
        ],
        "Enabled": true,
        "ScanEnabled": true,
        "Actions": [
            {
                "LambdaAction": {
                    "InvocationType": "Event",
                    "FunctionArn": "arn:aws:lambda:us-east-1:************:function:<_YOUR_LAMBDA_FUNCTION_>"
                }
            }
        ],
        "TlsPolicy": "Optional"
    }
}

jo コマンドで生成されるJSONは以下の通りです。

{
   "Name": "lambda-rule",
   "Recipients": [
      "<_YOUR_VERIFICATION_DOMAIN_>"
   ],
   "Enabled": true,
   "ScanEnabled": true,
   "Actions": [
      {
         "LambdaAction": {
            "InvocationType": "Event",
            "FunctionArn": "arn:aws:lambda:us-east-1:************:function:<_YOUR_LAMBDA_FUNCTION_>"
         }
      }
   ],
   "TlsPolicy": "Optional"
}

次にヘッダを追加するアクションのルールを作成してみます。SESではメール受信時に任意のヘッダをkey/value形式で追加することができます。以下のコマンドで設定します。

# ルールの作成
$ aws ses create-receipt-rule \
  --rule-set-name test-rule-set \
  --rule "$(jo Name=header-rule Recipients=$(jo -a $domain) Enabled=true ScanEnabled=true Actions=$(jo -a $(jo AddHeaderAction=$(jo HeaderName=TestHeader HeaderValue=Test))) TlsPolicy=Optional)"
# ルールの内容を確認
$ aws ses describe-receipt-rule \
  --rule-name header-rule \
  --rule-set-name test-rule-set
{
    "Rule": {
        "Name": "header-rule",
        "Recipients": [
            "<_YOUR_VERIFICATION_DOMAIN_>"
        ],
        "Enabled": true,
        "ScanEnabled": true,
        "Actions": [
            {
                "AddHeaderAction": {
                    "HeaderName": "TestHeader",
                    "HeaderValue": "Test"
                }
            }
        ],
        "TlsPolicy": "Optional"
    }
}

jo コマンドで生成されるJSONは以下の通りです。

{
   "Name": "header-rule",
   "Recipients": [
      "<_YOUR_VERIFICATION_DOMAIN_>"
   ],
   "Enabled": true,
   "ScanEnabled": true,
   "Actions": [
      {
         "AddHeaderAction": {
            "HeaderName": "TestHeader",
            "HeaderValue": "Test"
         }
      }
   ],
   "TlsPolicy": "Optional"
}

最後にバウンス用ルールを作成します。設定したドメインからメールを送信した際にバウンスの発生を契機としてアクションを定義することができます。指定可能な設定は以下の通りです。より詳細な情報はこちらのドキュメントを参照してください。

  • アクションを実行するSMTP Replyコード、SMTPステータスコード
    • よく利用するものがテンプレートとして事前に用意されている
    • より細かい設定も可能
  • Reply senderでバウンスメールの送信元アドレス
    • 検証されている必要がある
  • パブリッシュするSNSトピック

どうやらS3に保存されるメールにはこのルールにマッチした旨の内容が書かれていないようなので、SNSトピックを指定します。以下のコマンドでルールを作成してください。

# ルールの作成
$ aws ses create-receipt-rule \
  --rule-set-name test-rule-set \
  --rule "$(jo Name=bounce-rule Recipients=$(jo -a $domain) Enabled=true ScanEnabled=true Actions=$(jo -a $(jo BounceAction=$(jo Message=Mailbox_does_not_exist Sender=bounce@$domain SmtpReplyCode=\"550\" TopicArn="$(aws sns list-topics --query 'Topics[?contains(TopicArn,`'$sns_topic'`)].TopicArn' --output text)" StatusCode=5.1.1))) TlsPolicy=Optional | sed -E 's/_/ /g')"
# ルールの内容を確認
$ aws ses describe-receipt-rule \
  --rule-name bounce-rule \
  --rule-set-name test-rule-set
{
    "Rule": {
        "Name": "bounce-rule",
        "Recipients": [
            "<_YOUR_VERIFICATION_DOMAIN_>"
        ],
        "Enabled": true,
        "ScanEnabled": true,
        "Actions": [
            {
                "BounceAction": {
                    "Message": "Mailbox does not exist",
                    "Sender": "bounce@<_YOUR_VERIFICATION_DOMAIN_>",
                    "SmtpReplyCode": "550",
                    "TopicArn": "arn:aws:sns:us-east-1:************:<_YOUR_SNS_TOPIC_>",
                    "StatusCode": "5.1.1"
                }
            }
        ],
        "TlsPolicy": "Optional"
    }
}

jo コマンドで生成されるJSONは以下の通りです。

{
   "Name": "bounce-rule",
   "Recipients": [
      "<_YOUR_VERIFICATION_DOMAIN_>"
   ],
   "Enabled": true,
   "ScanEnabled": true,
   "Actions": [
      {
         "BounceAction": {
            "Message": "Mailbox does not exist",
            "Sender": "bounce@<_YOUR_VERIFICATION_DOMAIN_>",
            "SmtpReplyCode": "550",
            "TopicArn": "arn:aws:sns:us-east-1:************:<_YOUR_SNS_TOPIC_>",
            "StatusCode": "5.1.1"
         }
      }
   ],
   "TlsPolicy": "Optional"
}

4. ルールセットの有効化

作成したルールセットを有効化してメール受信時にルールが適用される設定をします。以下のコマンドで設定可能です。

# ルールセットの有効化
$ aws ses set-active-receipt-rule-set \
  --rule-set-name test-rule-set
# アクティブなルールセットの内容を表示
$ aws ses describe-active-receipt-rule-set
{
    "Rules": [
        {
            "Name": "bounce-rule",
<snip>

なお、ルールは aws ses describe-active-receipt-rule-set コマンドの出力結果順に適用されます(途中のルールでストップさせていない場合)。もしルールの適用順を変更したい場合は以下のコマンドで実施可能です。 --rule-names オプションで指定した順番に変更されます。

$ aws ses reorder-receipt-rule-set \
  --rule-set-name <_YOUR_RULE_SET_> \
  --rule-names rule1 rule2 ruleN

5. メール受信の動作確認

実際にメールを受信した場合に意図した動作をするのか確認してみます。今回の設定では正常にメールが受信された場合以下の動作をします。

  • S3バケットにメールがputされる
  • SNSトピックでメール内容がパブリッシュされる
  • Lambda関数がInvokeされる
  • 指定したヘッダが追加される

以下のコマンドでテスト用メールを送信して上記内容を確認できれば成功です。SNSは今回プロトコルとしてemailを選択したためメールが受信できたかどうかで確認しました。

# ルール適用対象のドメインに対してテスト用メールの送信
$ aws ses send-email \
  --from aaa@$domain \
  --to bbb@$domain \
  --subject subject \
  --text body
{
    "MessageId": "01000159402c3fe8-8509d38b-3b6f-48fc-9de3-67f9a4ef3f65-000000"
}
# S3バケットにメールがputされていることを確認
$ aws s3 ls s3://<_YOUR_S3_BUCKET_>-ses-test-bucket --recursive
2016-12-27 20:12:34       645  ses/AMAZON_SES_SETUP_NOTIFICATION
2016-12-27 21:13:24       3420 ses/2gsukgfsci7tandk2gk5bnf8du7em96sp1oe5n01
# Lambda関数がInvokeされていることを確認
$ aws logs get-log-events \
  --log-group-name <_YOUR_LOG_GROUP_> \
  --log-stream-name <_YOUR_LOG_STREAM_>
{
    "nextForwardToken": "f/33068454927968187177600468956770963601471987665037295618",
    "events": [
        {
            "ingestionTime": 1482840803477,
            "timestamp": 1482840803460,
            "message": "START RequestId: dcc0db47-cc2d-11e6-bfea-83a6994a8865 Version: $LATEST\n"
        },
<snip>
# 指定したヘッダが追加されていることを確認
$ aws s3 cp s3://<_YOUR_S3_BUCKET_>/ses/2gsukgfsci7tandk2gk5bnf8du7em96sp1oe5n01 - \
  | grep -F 'TestHeader'
TestHeader: Test

続いてバウンスが発生した場合の動作を確認します。AWSにはメールボックスシミュレータという各種メール送信時のテスト用メールアドレスを用意してくれています。該当のドキュメントはこちらです。バウンスのテストをする場合、自分でテスト用メールアドレスを利用してしまうとバウンス率が上昇し、最悪SESが使えなくなる場合があります。基本的にメールボックスシミュレータを利用しましょう。SMTP Replyコード550、SMTPステータスコード5.5.1を発生させるためには「bounce@simulator.amazonses.com」宛にメールを送信すればOKです。以下のコマンドでバウンスを発生させます。

$ aws ses send-email \
  --from aaa@$domain \
  --to bounce@simulator.amazonses.com \
  --subject subject \
  --text body
{
    "MessageId": "01000159407eaad6-cb4ee4a2-4ee0-4acb-a17b-5b581b72a34b-000000"
}

SNSトピックにパブリッシュされた内容を確認し、以下のような内容が記述されていればOKです。

  "action": {
    "type": "Bounce",
    "topicArn": "arn:aws:sns:us-east-1:************:<_YOUR_SNS_TOPIC_>",
    "smtpReplyCode": "550",
    "statusCode": "5.1.1",
    "message": "Mailbox does not exist",
    "sender": "bounce@<_YOUR_VERIFICATION_DOMAIN_>"
  }

まとめ

いかがだったでしょうか。

AWS CLIを利用したSESのメール受信環境を構築してみました。以前のエントリと合わせて基本的なセットアップ方法を理解することができました。今後はより高度な内容をお伝えできるよう、より理解を深めていきたいと思います。

本エントリがみなさんの参考になれば幸いに思います。