AWS Simple Email Service(SES)のConfiguration Setでメール開封とクリックを計測できるようにしてみた。

f:id:gurimmer:20170831125142j:plain Photo by Kirsty TG on Unsplash

こんにちは、@gorouです。 8月始めごろ、AWSのSimple Email Service(以下、SES)で送信したメールの開封・クリック計測ができるConfiguration Set機能のリリースが発表されました。

Amazon SES で顧客エンゲージメントを追跡するためのオープンおよびクリックメトリクスを導入

tenpuで実験的に実装を行ってみたので、機能の詳細を紹介します。

www.tenpu.me

SESの通知機能

SESでメールを送信した場合、イベントとして「到達不能(bounce)」「苦情(complaint)」「到達(delivery)」が通知されます。 SESを利用する上で気をつけなければいけないのが、「到達不能(bounce)」と「苦情(complaint)」の発生率を一定の比率以下に維持しないと利用停止となります。そのためイベント通知を受け取るのはとても重要な機能になります。

発生率の状況は、最近リリースされた「Reputation Dashboard」を見ると、現在の状態がわかるようになっています。

Amazon Simple Email Service (SES) で E メールアカウント用の評価ダッシュボードを導入

これらイベント通知設定は、SESで2箇所あり、それぞれで通知先・通知できるイベントが違います。

メニュー 取得できるイベント 通知先
Domain bounce
complaint
delivery
SNS Topic(SNSと同リージョンのみ)
Email Addresses bounce
complaint
delivery
SNS Topic(SNSと同リージョンのみ)

これに新しい通知設定として、Configuration Setが追加されました。Configuration Setはメール配信毎の設定ができ、 受信するイベントの選択・通知先を自由に選択ができ、かつ通知先を複数指定することができるため、とても柔軟に設定ができます。

f:id:gurimmer:20170831104133p:plain 1つのConfiguration SetにDestinationという通知設定を複数作成できる

また、今までのDomain・Email Addressには無かったSend・Reject・Open・Clickイベントの通知ができるようになっています。

メニュー 取得できるイベント 通知先
Configuration Sets Bounce
Click
Complaint
Delivery
Open
Reject
Send
CloudWatch
SNS
Firehose

各設定箇所の役割として、以下の用途で利用すると役割を分割できそうです。

  • Domain: このドメインのメールアドレス全体設定として機能
  • Email Addresses: このメールアドレスの設定として機能
  • Configuration Set: メール配信毎の設定として機能

Configuration Setの利用方法

AWS-SDK-PHP v3系の場合以下のように利用します。最新版のv3.32以降でないと ConfigurationSet パラメータは無視されるため気をつけましょう。また、メールはHTMLメールでなければOpenイベント・Clickイベント通知ができなくなるので注意です。

<?php
$credentials = new \Aws\Credentials\Credentials('aws_key', 'aws_secret');
$sdk = new Aws\Sdk([
    'region' => 'ap-northeast-1',
    'version' => 'latest',
    'credentials' => $credentials,
]);
$ses = $sdk->createSes();
$html = mb_convert_encoding('HTMLメールテスト<a href="http://example.com/test">リンク</a>', 'HTML-ENTITIES', 'UTF-8');
$result = $ses->sendEmail([
    'ConfigurationSetName' => 'test-configuration-set', // ConfigurationSetの名前を指定
    'Source' => 'from@example.com',
    'Destination' => [
        'ToAddresses' => ['to@example.com'],
    ],
    'Message' => [
        'Subject' => [
            'Data' => 'Test',
            'Charset' => 'UTF-8',
        ],
        'Body' => [
            'Html' => [
                'Data' => $html,
                'Charset' => 'UTF-8',
            ],
        ],
    ],
]);

if ($result) {
    echo $result['MessageId'];
}

結果で返ってくるMessageIdがメールのIdになるため、MessageIdでメールのイベントの経過を追うことができます。

送ったメール内のリンクは、AWSのURLに自動置換されます。自動置換されたくない場合、 ses:no-track をタグの属性として記述しておくと、そのタグのURLは自動置換されなくなります。(マークアップ的に書き方がNGなきがしますが…)

<?php
$html = mb_convert_encoding('HTMLメールテスト<a ses:no-track href="http://example.com/test">リンク</a>', 'HTML-ENTITIES', 'UTF-8');

ちなみにmailtoのリンクは自動置換されませんでした。

新しいイベントの通知されるデータ

既存からある「到達不能(bounce)」「苦情(complaint)」「到達(delivery)」はマニュアルを見ていただければ、通知されるデータが分かるので省略しますが、 新しく増えたSend・Reject・Open・Clickイベントはマニュアルに詳しく記載されていなかったため、簡単にまとめてみます。

共通部分(Notification)

Message の部分が各イベントでデータが代わります。Message部分はjsonがエンコードされた状態なので再度jsonデコードする必要があります。下でMessage部分は紹介するので省略しています。

{
  "Type": "Notification",
  "MessageId": "xxxx-xxx-xxx-xxx",
  "TopicArn": "arn:aws:sns:us-east-1:xxxx:test-configuration-set",
  "Subject": "Amazon SES Email Event Notification",
  "Message": "...",
  "Timestamp": "2017-08-29T02:41:02.392Z",
  "SignatureVersion": "1",
  "Signature": "xxxxxxx...",
  "SigningCertURL": "https://sns.us-east-1.amazonaws.com/xxxxxxx",
  "UnsubscribeURL": "https://sns.us-east-1.amazonaws.com/xxxxxxx"
}

共通部分(Message)

NotificationのMessageオブジェクトにも共通の項目があります。Messageオブジェクトにイベント毎の専用オブジェクトが追加されます。

{
  "eventType": "Send",
  "mail": {
    "timestamp": "2017-08-29T02:41:02.052Z",
    "source": "from@example.com",
    "sourceArn": "arn:aws:ses:us-east-1:xxxxxxx....",
    "sendingAccountId": "xxxxxxx",
    "messageId": "xxxx-xxxxx-xxxxx-xxxxx-xxxxx",
    "destination": [
      "to@example.com"
    ],
    "headersTruncated": false,
    "headers": [
      {
        "name": "X-SES-CONFIGURATION-SET",
        "value": "test-configuration-set"
      },
      {
        "name": "Subject",
        "value": "Test"
      },
      {
        "name": "From",
        "value": "from@example.com"
      },
      {
        "name": "MIME-Version",
        "value": "1.0"
      },
      {
        "name": "Content-Type",
        "value": "text/html "
      }
    ],
    "commonHeaders": {
      "from": [
        "from@example.com"
      ],
      "messageId": "xxx-xxxxxx-xxxxx-xxxxxx",
      "subject": "Test"
    },
    "tags": {
      "ses:configuration-set": [
        "test-configuration-set"
      ],
      "ses:source-ip": [
        "xxx.xx.xx.xx"
      ],
      "ses:from-domain": [
        "example.com"
      ]
    }
  },
  "send": {}
}

Notificationのjsonをjqコマンドで確認したい場合、以下のようにすると確認できます。Message部分がエンコードされているため、そのままだとテキスト扱いになってしまうので、Message部分をパースしたい場合、2回jqコマンドを通します。(-rオプションを付けているのは、Message部分に改行が含まれていてパースエラーになるためです)

cat sns_notification.json | jq -r '.Message' | jq

Sendイベント

送信が完了したタイミングで通知されます sendオブジェクトのみが追加されているだけで中身はないようです。

{
  "eventType": "Send",
  "mail": {
    省略...
  },
  "send": {}
}

Rejectイベント

AWS側のチェック(ウイルスチェックなど)でエラーの場合に通知されます。通知されたことがないため推測です。。。

{
  "eventType": "Reject",
  "mail": {
    省略...
  },
  "reject": {
    "reason": "xxxxxxxx"
  }
}

Openイベント

メールが開かれたタイミングで通知されます。gmailだと画像読み込みの許可が表示されますが、これを許可したタイミングで通知されます。 ※gmailの場合、Googleのbotが先にアクセスしてくることがあるようです。

{
  "eventType": "Open",
  "mail": {
    省略...
  },
  "open": {
    "timestamp": "2017-08-29T07:47:04.485Z",
    "userAgent": "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko Firefox/11.0 (via ggpht.com GoogleImageProxy)",
    "ipAddress": "xx.xx.xx.xxx"
  }
}

Clickイベント

メール内のリンクをクリックしたタイミングで通知されます。元のリンクURLが link に入っているためリンク毎の集計が可能です。

{
  "eventType": "Click",
  "mail": {
    省略...
  },
  "click": {
    "timestamp": "2017-08-29T07:47:27.229Z",
    "ipAddress": "xx.xx.xx.xxx",
    "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
    "link": "http://example.com/test",
    "linkTags": null
  }
}

まとめ

SESのConfiguration Setについてまとめました。AWS側のドキュメント整備が進んでいないのか、Reject・Open・Clickあたりのドキュメントが見つからず*1、実際に試しながら調査する感じになりました。Open・Clickが集計できるようになったことで、開封率・流入率など計測できるようになるのもいいですね。

最初、SDKのライブラリのバージョンを最新にせずに試してしまい、全く動かず数時間を失ったことが悔しいので、みなさんライブラリのバージョンは最新にしてから試しましょう。

*1:探してみると、firehose側のマニュアルに記載されていました。docs.aws.amazon.com