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

はじめに

こんにちは、中山です。

AWSが提供しているフルマネージド型Eメール配信サービスであるAmazon Simple Email Service(以下SES)、みなさんはお使いになっているでしょうか。執筆時点(2016/12/26)でまだ東京リージョンに来ていないことや、日本の携帯キャリアとの相性などを理由として興味はあるがまだ導入されていない方も多いかとお思います。私も今まであまり触ったことが無かったということもあり、自分のお勉強目的も兼ねて「あえて」AWS CLIを利用したSESの設定方法をご紹介したいと思います。今回は北部バージニアリージョン上にSESのメール送信環境を構築していきます。

なお、検証に利用したAWS CLIのバージョンは現在の最新バージョンである1.11.34です。バージョンによって内容が変更される可能性があります。その点ご了承ください。

やってみる

上述の通り、今回は北部バージニアリージョンを利用します。 --region オプションで都度指定するのは面倒なので AWS_DEFAULT_REGIONus-east-1 を入れておきましょう。また、利用するドメインはすでに取得済みかつ権威サーバとしてRoute53を利用している前提で進めます。

1. ドメインの検証

まず最初に利用するドメインの検証を実施します。

# 利用するドメインを変数に代入
$ domain="<_YOUR_VERIFICATION_DOMAIN_>"
# ドメインの検証
$ aws ses verify-domain-identity \
  --domain "$domain"
{
    "VerificationToken": "<_YOUR_VERIFICATION_TOKEN_>"
}
# ドメインがアイデンティティ(ドメインとメールアドレスを管理する概念)に登録されていることを確認
$ aws ses list-identities
{
    "Identities": [
        "<_YOUR_VERIFICATION_DOMAIN_>"
    ]
}
# 現在の検証ステータスを確認
$ aws ses get-identity-verification-attributes \
  --identities $domain \
  --query 'VerificationAttributes.*.VerificationStatus' \
  --output text
Pending

aws ses verify-domain-identity コマンドを実行すると検証用トークンが生成されます。このトークンをDNSのTXTレコードに登録することにより、そのドメインの所有者であることを証明することが可能です。この設定をしないと検証ステータスが検証済みになりません。こちらのドキュメントにあるように、基本的には以下の形式でレコードセットを登録します。ただし、DNSプロバイダによってアンダースコアを含むレコードの登録に対応していない場合があるようです。その場合はドキュメントを参照して適宜変更してください。

Name Type Value
_amazonses.<_YOUR_VERIFICATION_DOMAIN>. TXT "<_YOUR_VERIFICATION_TOKEN_>"

以下のコマンドを実行してTXTレコードを作成します。

# TXTレコードの作成
$ 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=_amazonses.$domain. Type=TXT TTL=1800 ResourceRecords=$(jo -a $(jo Value=<_YOUR_VERIFICATION_TOKEN_>))))) \
    | sed -E -e 's@("Value":)@\1"\\@' -e 's@(=)@\1\\"@')"
{
    "ChangeInfo": {
        "Status": "PENDING",
        "Comment": "test",
        "SubmittedAt": "2016-12-26T04:14:18.773Z",
        "Id": "/change/CENTD2CLA3PTN"
    }
}
# 名前解決ができることを確認
$ dig +short _amazonses.$domain TXT
"<_YOUR_VERIFICATION_TOKEN_>"

jo コマンドでゴニョゴニョしていますが生成されるJSONは以下の通りです。TXTレコードはバリューにダブルクオートを含ませた形で登録する必要があるため jo コマンド単体ではうまく処理できませんでした。。。この部分は sed で加工してます。

{
   "Comment": "test",
   "Changes": [
      {
         "Action": "UPSERT",
         "ResourceRecordSet": {
            "Name": "_amazonses.<_YOUR_VERIFICATION_DOMAIN_>.",
            "Type": "TXT",
            "TTL": 1800,
            "ResourceRecords": [
               {
                  "Value":"\"<_YOUR_VERIFICATION_TOKEN_>\""
               }
            ]
         }
      }
   ]
}

暫く待った後、AWSアカウントに関連付けられたEメールアドレス宛てに「Amazon SES Domain Verification SUCCESS」と記載されたメールが届けばドメインが正常に検証されたことを確認できます。以下のコマンドでも確認可能です。

$ aws ses get-identity-verification-attributes \
  --identities $domain \
  --query 'VerificationAttributes.*.VerificationStatus' \
  --output text
Success

2. DKIMの有効化

続いてDKIMの設定をしましょう。この設定は検証済みドメインでないと実施できないことにご注意ください。なお、DKIMと後ほど設定するSPFについては以下のエントリが参考になります。

まずは先程作成した検証済みドメインに対してDKIM用トークンを生成します。

# 現在のDKIM設定を確認
$ aws ses get-identity-dkim-attributes \
  --identities $domain \
  --query 'DkimAttributes.*.DkimVerificationStatus' \
  --output text
NotStarted
# DKIM用トークンの生成
$ aws ses verify-domain-dkim \
  --domain $domain
{
    "DkimTokens": [
        "<_YOUR_DKIM_TOKEN_1>",
        "<_YOUR_DKIM_TOKEN_2>",
        "<_YOUR_DKIM_TOKEN_3>"
    ]
}

aws ses verify-domain-dkim コマンドで生成されたトークンを先程と同じようにレコードセットに登録する必要があります。今回はTXTレコードではなくCNAMEレコードです。こちらのドキュメントにあるように以下の形式で登録します。

Name Type Value
<_YOUR_DKIM_TOKEN_1_>._domainkey.<_YOUR_VERIFICATION_DOMAIN_>. CNAME <_YOUR_DKIM_TOKEN_1_>.dkim.amazonses.com
<_YOUR_DKIM_TOKEN_2_>._domainkey.<_YOUR_VERIFICATION_DOMAIN_>. CNAME <_YOUR_DKIM_TOKEN_2_>.dkim.amazonses.com
<_YOUR_DKIM_TOKEN_3_>._domainkey.<_YOUR_VERIFICATION_DOMAIN_>. CNAME <_YOUR_DKIM_TOKEN_3_>.dkim.amazonses.com

以下のコマンドを実行してレコードセットを作成します。

# CNAMEレコードの作成
$ hosted_zone_id="$(
  aws route53 list-hosted-zones \
    --query 'HostedZones[?Name==`<_YOUR_DOMAIN_>`].Id' \
    --output text \
  | sed -E 's@/hostedzone/@@')";
  for dkim_token in $(
    aws ses get-identity-dkim-attributes \
      --identities $domain \
      --query 'DkimAttributes.*.DkimTokens' \
      --output text); do \
    aws route53 change-resource-record-sets \
      --hosted-zone-id $hosted_zone_id \
      --change-batch "$(jo Comment="test" Changes=$(jo -a $(jo Action=UPSERT ResourceRecordSet=$(jo Name=$dkim_token._domainkey.$domain. Type=CNAME TTL=1800 ResourceRecords=$(jo -a $(jo Value=$dkim_token.dkim.amazonses.com))))))";
  done
{
    "ChangeInfo": {
        "Status": "PENDING",
        "Comment": "test",
        "SubmittedAt": "2016-12-26T07:58:19.434Z",
        "Id": "/change/C1SQW59P84TYLC"
    }
}
{
    "ChangeInfo": {
        "Status": "PENDING",
        "Comment": "test",
        "SubmittedAt": "2016-12-26T07:58:21.488Z",
        "Id": "/change/C1WSG8QLWZ0UJE"
    }
}
{
    "ChangeInfo": {
        "Status": "PENDING",
        "Comment": "test",
        "SubmittedAt": "2016-12-26T07:58:23.474Z",
        "Id": "/change/C3RDER1CVOS3DH"
    }
}
# 名前解決ができることを確認
$ for dkim_token in $(
  aws ses get-identity-dkim-attributes \
    --identities $domain \
    --query 'DkimAttributes.*.DkimTokens' \
    --output text); do \
  dig +short $dkim_token._domainkey.$domain CNAME;
done
<_YOUR_DKIM_TOKEN_1_>.dkim.amazonses.com.
<_YOUR_DKIM_TOKEN_2_>.dkim.amazonses.com.
<_YOUR_DKIM_TOKEN_3_>.dkim.amazonses.com.

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

{
   "Comment": "test",
   "Changes": [
      {
         "Action": "UPSERT",
         "ResourceRecordSet": {
            "Name": "<_YOUR_DKIM_TOKEN_1_>._domainkey.<_YOUR_VERIFICATION_DOMAIN_>.",
            "Type": "CNAME",
            "TTL": 1800,
            "ResourceRecords": [
               {
                  "Value": "<_YOUR_DKIM_TOKEN_1_>.dkim.amazonses.com"
               }
            ]
         }
      }
   ]
}
{
   "Comment": "test",
   "Changes": [
      {
         "Action": "UPSERT",
         "ResourceRecordSet": {
            "Name": "<_YOUR_DKIM_TOKEN_2_>._domainkey.<_YOUR_VERIFICATION_DOMAIN_>.",
            "Type": "CNAME",
            "TTL": 1800,
            "ResourceRecords": [
               {
                  "Value": "<_YOUR_DKIM_TOKEN_2_>.dkim.amazonses.com"
               }
            ]
         }
      }
   ]
}
{
   "Comment": "test",
   "Changes": [
      {
         "Action": "UPSERT",
         "ResourceRecordSet": {
            "Name": "<_YOUR_DKIM_TOKEN_3_>._domainkey.<_YOUR_VERIFICATION_DOMAIN_>.",
            "Type": "CNAME",
            "TTL": 1800,
            "ResourceRecords": [
               {
                  "Value": "<_YOUR_DKIM_TOKEN_3_>.dkim.amazonses.com"
               }
            ]
         }
      }
   ]
}

CNAMEレコードの作成後、「Amazon SES DKIM setup SUCCESS」という件名のメールが届けば成功です。以下のコマンドでも確認できます。

$ aws ses get-identity-verification-attributes \
  --identities $domain \
  --query 'VerificationAttributes.*.VerificationStatus' \
  --output text
Success

上記コマンドの DkimEnabled という結果でDKIMの有効/無効の状態を確認できます。デフォルトで有効化されているようです。以下のコマンドでこの設定を切り替えられます。

# 無効化
$ aws ses set-identity-dkim-enabled \
  --identity $domain \
  --no-dkim-enabled
# 確認
$ aws ses get-identity-dkim-attributes \
  --identities $domain \
  --query 'DkimAttributes.*.DkimEnabled' \
  --output text
False
# 有効化
$ aws ses set-identity-dkim-enabled \
  --identity $domain \
  --dkim-enabled
# 確認
$ aws ses get-identity-dkim-attributes \
  --identities $domain \
  --query 'DkimAttributes.*.DkimEnabled' \
  --output text
True

3. SPFレコードの設定

続いてSPFレコードの設定をしていきます。こちらのドキュメントにあるように、SESはデフォルトでMAIL FROMヘッダをamazonses.comまたはそのサブドメインに設定してメールを送信します。メール送信サーバはSESなので特にSPFレコードの設定をしなくてもSPFのチェックをパスすることが可能です。ただし、MAIL FROMドメインを変更したい場合はメール送信サーバとドメインが一致しないためSPFレコードをセットアップする必要があります。今回はテスト目的でこの設定もやってみます。

# 現在の設定を確認
$ aws ses get-identity-mail-from-domain-attributes \
  --identities $domain \
  --query 'MailFromDomainAttributes.*'
[
    {
        "BehaviorOnMXFailure": "UseDefaultValue"
    }
]
# カスタムMAIL FROMドメインの有効化
$ aws ses set-identity-mail-from-domain \
  --identity $domain \
  --mail-from-domain bounce.$domain
# 現在の設定を確認
$ aws ses get-identity-mail-from-domain-attributes \
  --identities $domain \
  --query 'MailFromDomainAttributes.*.MailFromDomainStatus' \
  --output text
Pending

この時点では MailFromDomainStatusPending のままです。こちらのドキュメントにあるように、最低限MAIL FROMに指定したドメインに対してMXレコードを設定すればよいのですが、SPFも利用するのでその設定をしていきます。以下の内容でRoute53にレコードセットを作成します(今回はbounceというサブドメインを指定しています)。MXレコードの設定はリージョン毎に異なるのでご注意ください。

Name Type Value
bounce.<_YOUR_VERIFICATION_DOMAIN_> MX 10 feedback-smtp.us-east-1.amazonses.com
bounce.<_YOUR_VERIFICATION_DOMAIN_> TXT "v=spf1 include:amazonses.com ~all"

以下のコマンドを実行してレコードセットを作成します。

# MXレコードの作成
$ aws route53 change-resource-record-sets \
  --hosted-zone-id "$(
    aws route53 list-hosted-zones \
      --query 'HostedZones[?Name==`knakayama.me.`].Id' \
      --output text \
    | sed -E 's@/hostedzone/@@')" \
  --change-batch "$(jo -p Comment="test" Changes=$(jo -a $(jo Action=UPSERT ResourceRecordSet=$(jo Name=bounce.$domain. Type=MX TTL=300 ResourceRecords=$(jo -a $(jo Value=10_feedback-smtp.us-east-1.amazonses.com))))) | sed -E 's/10_/10 /')"
{
    "ChangeInfo": {
        "Status": "PENDING",
        "Comment": "test",
        "SubmittedAt": "2016-12-27T06:45:53.468Z",
        "Id": "/change/C3VU80OO7YYAE7"
    }
}
# SPFレコードの作成
$ aws route53 change-resource-record-sets \
  --hosted-zone-id "$(
    aws route53 list-hosted-zones \
      --query 'HostedZones[?Name==`knakayama.me.`].Id' \
      --output text \
    | sed -E 's@/hostedzone/@@')" \
  --change-batch "$(jo -p Comment="test" Changes=$(jo -a $(jo Action=UPSERT ResourceRecordSet=$(jo Name=bounce.$domain. Type=TXT TTL=300 ResourceRecords=$(jo -a $(jo Value=dummy))))) | sed -E 's/dummy/\\"v=spf1 include:amazonses.com ~all\\"/')"
{
    "ChangeInfo": {
        "Status": "PENDING",
        "Comment": "test",
        "SubmittedAt": "2016-12-27T06:50:08.370Z",
        "Id": "/change/CKFZUXOTLMEUY"
    }
}

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

{
   "Comment": "test",
   "Changes": [
      {
         "Action": "UPSERT",
         "ResourceRecordSet": {
            "Name": "bounce.<_YOUR_VERIFICATION_DOMAIN_>.",
            "Type": "MX",
            "TTL": 300,
            "ResourceRecords": [
               {
                  "Value": "10 feedback-smtp.us-east-1.amazonses.com"
               }
            ]
         }
      }
   ]
}
{
   "Comment": "test",
   "Changes": [
      {
         "Action": "UPSERT",
         "ResourceRecordSet": {
            "Name": "bounce._YOUR_VERIFICATION_DOMAIN_>.",
            "Type": "TXT",
            "TTL": 300,
            "ResourceRecords": [
               {
                  "Value": "\"v=spf1 include:amazonses.com ~all\""
               }
            ]
         }
      }
   ]
}

しばらくすると「Amazon SES Custom MAIL FROM Domain Setup SUCCESS」という件名のメールが届くと設定が完了したことを確認できます。以下のコマンドでも確認可能です。

$ aws ses get-identity-mail-from-domain-attributes \
  --identities $domain \
  --query 'MailFromDomainAttributes.*.MailFromDomainStatus' \
  --output text
Success

4. 送信先メールアドレスの検証

SESの初期状態ではサンドボックスと呼ばれる機能が制限された環境上に配置されます。サンドボックスでは検証済みメールアドレスにのみメール送信が可能です。今回はサンドボックス環境上での設定方法をご紹介します。サンドボックス外に移動するには以下のエントリを参照してください。

ちなみに、現在の送信制限に関する情報は以下のコマンドで確認できます。

$ aws ses get-send-quota
{
    "Max24HourSend": 200.0,
    "SentLast24Hours": 4.0,
    "MaxSendRate": 1.0
}

以下のコマンドを実行して送信先メールアドレスの検証を実施します。

# 検証するメールアドレスを変数に格納
$ email=<_YOUR_EMAIL_ADDRESS_>
# メールアドレスの検証
$ aws ses verify-email-identity \
  --email-address $email
# 現在の状態を確認
$ aws ses get-identity-verification-attributes \
  --identities $email \
  --query 'VerificationAttributes.*.VerificationStatus' \
  --output text
Pending

対象メールアドレス宛に「Amazon SES Address Verification Request」という件名のメールが届きます。本文に認証用URLが含まれているのでクリックします。クリックすると「Congratulations!」という画面に切り替わるので検証が完了したことを確認できます。以下のコマンドでも確認可能です。

$ aws ses get-identity-verification-attributes \
  --identities $email \
  --query 'VerificationAttributes.*.VerificationStatus' \
  --output text
Success

ちなみに、AWS CLIではSES用Waiterとしてアイデンティティの存在確認に対応しています。もしシェルスクリプトでアイデンティティの検証後、それに対して何らかの処理を行う場合は利用すると便利かと思います。

# 検証されるまで待つ
$ aws ses wait identity-exists \
  --identities hoge@example.com

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

最後に検証済みのドメインからメールアドレス宛てに正常にメール送信できるのか確認してみます。なお、SESを利用してメールを送信するには ses:SendRawEmail または ses:SendEmail 権限が必要なのでご注意ください。AWS CLIからメールを送信するには aws ses send-email または aws ses send-raw-email コマンドが利用できます。 raw-email の方は --raw-messages オプションでSMTPヘッダを含んだJSONファイルを指定する必要があります。今回はお手軽に send-email を使ってメールを送信してみます。

$ aws ses send-email \
  --from aaa@$domain \
  --to $email \
  --subject subject \
  --text body
{
    "MessageId": "010001593f0fda62-330c7f6c-ae48-493b-877f-03f20f4fbbfe-000000"
}

実行後、対象のメールアドレス宛に --from オプションで指定したメールアドレスからメールが届いたら成功です。メールのソースを見るとDKIMとSPFがそれぞれPASSされていることが確認できます。

Delivered-To: <_YOUR_EMAIL_ADDRESS_>
Received: by 10.129.104.5 with SMTP id d5csp3441833ywc;
        Mon, 26 Dec 2016 22:55:21 -0800 (PST)
X-Received: by 10.200.39.51 with SMTP id g48mr28283593qtg.256.1482821721517;
        Mon, 26 Dec 2016 22:55:21 -0800 (PST)
Return-Path: <010001593f0fda62-330c7f6c-ae48-493b-877f-03f20f4fbbfe-000000@bounce.<_YOUR_VERIFICATION_DOMAIN_>>
Received: from a8-61.smtp-out.amazonses.com (a8-61.smtp-out.amazonses.com. [54.240.8.61])
        by mx.google.com with ESMTPS id 50si27236478qtp.278.2016.12.26.22.55.21
        for <<_YOUR_EMAIL_ADDRESS_>>
        (version=TLS1 cipher=ECDHE-RSA-AES128-SHA bits=128/128);
        Mon, 26 Dec 2016 22:55:21 -0800 (PST)
Received-SPF: pass (google.com: domain of 010001593f0fda62-330c7f6c-ae48-493b-877f-03f20f4fbbfe-000000@bounce.<_YOUR_VERIFICATION_DOMAIN_> designates 54.240.8.61 as permitted sender) client-ip=54.240.8.61;
Authentication-Results: mx.google.com;
       dkim=pass header.i=@<_YOUR_VERIFICATION_DOMAIN_>;
       dkim=pass header.i=@amazonses.com;
       spf=pass (google.com: domain of 010001593f0fda62-330c7f6c-ae48-493b-877f-03f20f4fbbfe-000000@bounce.<_YOUR_VERIFICATION_DOMAIN_> designates 54.240.8.61 as permitted sender) smtp.mailfrom=010001593f0fda62-330c7f6c-ae48-493b-877f-03f20f4fbbfe-000000@bounce.<_YOUR_VERIFICATION_DOMAIN_>
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/simple; s=eehi34xfcgsfwf6lvvagts43lpgtfur5; d=<_YOUR_VERIFICATION_DOMAIN_>; t=1482821720; h=From:To:Subject:MIME-Version:Content-Type:Content-Transfer-Encoding:Message-ID:Date; bh=Ck5SoRNWUpSR4X0COv7R5ub2pUTtl6xz4dTFz++ji4M=; b=ZmSfAXP/6mAB8G1N//9w/C94c/C9IoFfZXED5AZEppjPOdkUkRpLx4Bwqow8oLoi fHdco+BXVBWQG0UjnnYE9QugtcJAplW7sT2SgyO1qDsUncG0+qweMEnA9NsUQO+5U0S nhhWmlofHOPX1o+Yxq+2sqkb9gFdCyGXFc65vmKI=
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/simple; s=6gbrjpgwjskckoa6a5zn6fwqkn67xbtw; d=amazonses.com; t=1482821720; h=From:To:Subject:MIME-Version:Content-Type:Content-Transfer-Encoding:Message-ID:Date:Feedback-ID; bh=Ck5SoRNWUpSR4X0COv7R5ub2pUTtl6xz4dTFz++ji4M=; b=gwydGOcMo3V2O5ICQcgPWC+dMtkLS/JFPamDOlDsmF3kI+lAPENbfFkUbT2+dyS7 XWPOzKU8G96v7eG99A8Rom/c+UFQE408lBTzDclWUs7HcCnA+Omw/m5bRVpsGzBerSf 0lXus+Tx5euUNLmChtZ+O3kc9o4kNyIeOxvhCoFI=
From: aaa@<_YOUR_VERIFICATION_DOMAIN_>
To: <_YOUR_EMAIL_ADDRESS_>
Subject: subject
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 7bit
Message-ID: <010001593f0fda62-330c7f6c-ae48-493b-877f-03f20f4fbbfe-000000@email.amazonses.com>
Date: Tue, 27 Dec 2016 06:55:20 +0000
X-SES-Outgoing: 2016.12.27-54.240.8.61
Feedback-ID: 1.us-east-1.j0pKVLSfUqd0DB5cOyTSnY6NlRZdK+KOO22UWgklVl8=:AmazonSES

body

ちなみにですが、以下のコマンドを実行することにより15分毎のメール送信数の統計を確認できます。

$ aws ses get-send-statistics
{
    "SendDataPoints": [
        {
            "Complaints": 0,
            "Timestamp": "2016-12-26T02:58:00Z",
            "DeliveryAttempts": 1,
            "Bounces": 0,
            "Rejects": 0
        },
        {
            "Complaints": 0,
            "Timestamp": "2016-12-26T02:43:00Z",
            "DeliveryAttempts": 1,
            "Bounces": 0,
            "Rejects": 0
        },
        {
            "Complaints": 0,
            "Timestamp": "2016-12-26T09:13:00Z",
            "DeliveryAttempts": 3,
            "Bounces": 0,
            "Rejects": 0
        }
    ]
}

まとめ

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

AWS CLIを利用してSESのメール送信環境を構築してみました。やはりAWS CLIで設定すると細かい設定を明示的にする必要があるので、お勉強用途には最適だなと思います。次回はメール受信環境をセットアップしてみます。

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