2014/07/27

Amazon SNSを使ってSESメールのホワイトリスト管理

ご無沙汰です。

メールマガジンって色々大変ですよね?私がその大変さを実感したのが最近ばかりのことですが、今日はAmazonのSimple Notification Serviceを使ってホワイトリストの管理を簡単にできる方法を紹介させてもらいたいと思います。

SESを使ってメールを送信した時に、ユーザのメールアドレスが存在しなかったため送信完了できなかった場合(Bounce)や、ユーザが「迷惑メール」のボタンを押した(Complaint)場合、Amazonからエラーの詳細が書いてあるメールを送ってもらうのがデフォルトの設定かと思いますが、それ以外にもSNSの通信を送ってもらうこともできます。

そしてSNSにはHTTPSのインタフェースもあるので、簡単なAPIを立ち上げて、BounceとComplaintを自動的にブラックリスト化をすることが意外と簡単です。

SNS Topicの作成の仕方や送信先(endpoint)の認証の仕方が丁寧にドキュメントに書いてありますが、BounceとComplaintの場合どんなメッセージが書いてくるかというと、こんな感じです:

POST / HTTP/1.1
x-amz-sns-message-type: Notification
x-amz-sns-message-id: 22b80b92-fdea-4c2c-8f9d-bdfb0c7bf324
x-amz-sns-topic-arn: arn:aws:sns:us-east-1:123456789012:MyTopic
x-amz-sns-subscription-arn: arn:aws:sns:us-east-1:123456789012:MyTopic:c9135db0-26c4-47ec-8998-413945fb5a96
Content-Length: 773
Content-Type: text/plain; charset=UTF-8
Host: example.com
Connection: Keep-Alive
User-Agent: Amazon Simple Notification Service Agent

{
  "Type" : "Notification",
  "MessageId" : "22b80b92-fdea-4c2c-8f9d-bdfb0c7bf324",
  "TopicArn" : "arn:aws:sns:us-east-1:123456789012:MyTopic",
  "Message" : "{\\"notificationType\\":\\"Bounce\\",\\"bounce\\":{\\"bounceSubType\\":\\"General\\",\\"bounceType\\":\\"Permanent\\",\\"reportingMTA\\":\\"dsn; a8-41.smtp-out.amazonses.com\\",   \\"bouncedRecipients\\":[{\\"status\\":\\"5.1.1\\",\\"action\\":\\"failed\\",\\"diagnosticCode\\":\\"smtp; 554 5.1.1 <recipient@example.com>: Recipient address rejected: User unknown\\",      \\"emailAddress\\":\\"recipient@example.com\\"}],\\"timestamp\\":\\"2014-07-27T09:39:22.070Z\\",\\"feedbackId\\":\\"00000147773054e9-96ae8a22-c833-4967-874c-d56accc9fd2d-000000\\"},\\"mail\\":{\\"timestamp\\":\\"2014-07-27T09:39:18.000Z\\",\\"source\\":\\"noreply@example.come\\",\\"messageId\\":\\"0000014777304606-b5a16bd1-5a20-40ef-993b-0395f14de101-000000\\",\\"destination\\":         [\\"recipient@example.com\\"]}}",
  "Timestamp" : "2012-05-02T00:54:06.655Z",
  "SignatureVersion" : "1",
  "Signature" : "EXAMPLEw6JRNwm1LFQL4ICB0bnXrdB8ClRMTQFGBqwLpGbM78tJ4etTwC5zU7O3tS6tGpey3ejedNdOJ+1fkIp9F2/LmNVKb5aFlYq+9rk9ZiPph5YlLmWsDcyC5T+Sy9/umic5S0UQc2PEtgdpVBahwNOdMW4JPwk0kAJJztnc=",
  "SigningCertURL" : "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem",
  "UnsubscribeURL" : "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:123456789012:MyTopic:c9135db0-26c4-47ec-8998-413945fb5a96"
  }

リクエストbodyのJSONなのですが、JSONなのにContent-Typeが「text/plain」のでご注意ください。そして、今回の話題のブラックリストに入れたいメールアドレスがMessageの項目に入っています。エスケープされているJSONなので、これもデコード必要ですね。

私はPlack::Requestからrawのリクエストbodyを取得して、デコードしてみました。


if ($request->headers->header('x-amz-sns-message-type') eq "Notification") {
        my $raw_body = $request->raw_body;
        $raw_body =~ s/\\\\/\\/g;

        my $decoder = JSON::XS->new->utf8;
        my $json = $decoder->decode($raw_body) or die "could not decode json";
        my $message = $decoder->decode($json->{Message}) or die "could not decode json";

        my @addresses;
        if ($message->{notificationType} eq "Bounce") {
            my $bounced_recipients = $message->{bounce}->{bouncedRecipients};
            @addresses = map { $_->{emailAddress} } @$bounced_recipients;

        } elsif ($message->{notificationType} eq "Complaint") {
            my $complaining_recipients = $message->{complaint}->{complainedRecipients};
            @addresses = map { $_->{emailAddress} } @$complaining_recipients;

        }

        for my $address (@addresses) {
            #対象メールアドレスをブラックリストに追加
        }
}


意外と簡単でした!これでばっちり!