AWS SNS 自定义邮件格式

AWS 的 SES (Simple Email Service) 可以提供邮件收发服务。对于邮件收发的反馈,如 Bounce message, 可以发送到 SNS (Simple Notification Service) 作进一步处理。SNS 可以指定某个邮件地址作为订阅者,将消息发送到该邮箱中。然而,SNS发出来的邮件是 JSON,可读性不好,例如:

{"notificationType":"Delivery","mail":{"timestamp":"2019-02-18T06:03:02.669Z","source":"kfc@feichashao.com","sourceArn":"arn:aws:ses:us-west-2:xxxxxx:identity/feichashao.com","sourceIp":"205.251.234.36","sendingAccountId":"xxxxxx","messageId":"01010168ff335c8d-f00ce1c1-e103-49cd-912f-9f397c7a463c-000000","destination":["feichashao@gmail.com"],"headersTruncated":false,"headers":[{"name":"From","value":"kfc@feichashao.com"},{"name":"To","value":"feichashao@gmail.com"},{"name":"Subject","value":"free kfc"},{"name":"MIME-Version","value":"1.0"},{"name":"Content-Type","value":"text/plain; charset=UTF-8"},{"name":"Content-Transfer-Encoding","value":"7bit"}],"commonHeaders":{"from":["kfc@feichashao.com"],"to":["feichashao@gmail.com"],"subject":"free kfc"}},"delivery":{"timestamp":"2019-02-18T06:03:03.917Z","processingTimeMillis":1248,"recipients":["feichashao@gmail.com"],"smtpResponse":"250 2.0.0 OK  1550469783 q2si13329671plh.79 - gsmtp","remoteMtaIp":"74.125.20.27","reportingMTA":"a27-30.smtp-out.us-west-2.amazonses.com"}}

怎么能让这个提醒邮件变得更加友好呢? SNS目前不支持自定义邮件格式。一个思路是,将 SNS 的消息发送到 Lambda 上,让 Lambda 处理好格式后,再发送到指定邮箱。即 SES -> SNS -> Lambda -> SES.

创建 SNS Topic

创建一个SNS Topic, 比如名字叫"email-notify".

创建 Lambda Function

使用 Lambda 的 Blueprints ses-notification-python 作为模板,创建以下 Lambda Function.
这里主要修改了 handle_delivery 这个 Handler, 期望每次 SES 发送邮件后,都会触发这个 Handler, 将相关信息发送到管理员邮箱。

from __future__ import print_function
import json
import boto3
from botocore.exceptions import ClientError


def lambda_handler(event, context):
    message = json.loads(event['Records'][0]['Sns']['Message'])
    notification_type = message['notificationType']
    handlers.get(notification_type, handle_unknown_type)(message)


def handle_bounce(message):
    message_id = message['mail']['messageId']
    bounced_recipients = message['bounce']['bouncedRecipients']
    addresses = list(
        recipient['emailAddress'] for recipient in bounced_recipients
    )
    bounce_type = message['bounce']['bounceType']
    print("Message %s bounced when sending to %s. Bounce type: %s" %
          (message_id, ", ".join(addresses), bounce_type))


def handle_complaint(message):
    message_id = message['mail']['messageId']
    complained_recipients = message['complaint']['complainedRecipients']
    addresses = list(
        recipient['emailAddress'] for recipient in complained_recipients
    )
    print("A complaint was reported by %s for message %s." %
          (", ".join(addresses), message_id))


def handle_delivery(message):
    message_id = message['mail']['messageId']
    delivery_timestamp = message['delivery']['timestamp']
    header_from = message['mail']['commonHeaders']['from'][0]
    header_to = message['mail']['commonHeaders']['to'][0]
    print("Message %s was delivered successfully at %s" %
          (message_id, delivery_timestamp))
    SENDER = "feichashao-ses <feichashao@gmail.com>"
    RECIPIENT = "feichashao@qq.com"
    AWS_REGION = "us-west-2"
    SUBJECT = "Amazon SES Test (SDK for Python)"
    BODY_TEXT = ("Amazon SES Test (Python)\r\n"
             "This email was sent with Amazon SES using the "
             "AWS SDK for Python (Boto)."
            )

    BODY_HTML = """<html>
    <head></head>
    <body>
    <h1>Email Delivery</h1>
    <p>From: {}</p>
    <p>To: {}</p>
    </body>
    </html>
    """.format(header_from, header_to)
    CHARSET = "UTF-8"
    client = boto3.client('ses',region_name=AWS_REGION)
   
    try:
        #Provide the contents of the email.
        response = client.send_email(
            Destination={
                'ToAddresses': [
                    RECIPIENT,
                ],
            },
            Message={
                'Body': {
                    'Html': {
                        'Charset': CHARSET,
                        'Data': BODY_HTML,
                    },
                    'Text': {
                        'Charset': CHARSET,
                        'Data': BODY_TEXT,
                    },
                },
                'Subject': {
                    'Charset': CHARSET,
                    'Data': SUBJECT,
                },
            },
            Source=SENDER,
        )
    # Display an error if something goes wrong.
    except ClientError as e:
        print(e.response['Error']['Message'])
    else:
        print("Email sent! Message ID:"),
        print(response['MessageId'])



def handle_unknown_type(message):
    print("Unknown message type:\n%s" % json.dumps(message))
    raise Exception("Invalid message type received: %s" %
                    message['notificationType'])


handlers = {"Bounce": handle_bounce,
            "Complaint": handle_complaint,
            "Delivery": handle_delivery}

在 Designer 中,将刚刚创建的 SNS 作为 Trigger。

创建 SES 服务

上面两个都是为 SES 服务的。现在,创建一个 SES 服务。认证一个 Domain 作为邮件发送方,认证一个 Email 作为邮件接收方。

点进 SES 的 Domain(以 feichashao.com 为例),点开 Notification 一栏,点击 Edit Configuration, 将 Deliveries 选择为上面的 SNS topic,如 "email-notify".

回到 Domain 的界面,点击 Send a Test Email 进行测试。发送者是 test1@feichashao.com,收件人是 feichashao@gmail.com。在邮件发送成功后,Lambda 应该会收到 SNS 的信息,然后 Lambada 会把 JSON 整理后,发送到管理员邮箱 feichashao@qq.com.

Email Delivery
From: test4@feichashao.com
To: feichashao@gmail.com