如何将原始电子邮件 (MIME) 从 AWS SES 转换为 Gmail?

Posted

技术标签:

【中文标题】如何将原始电子邮件 (MIME) 从 AWS SES 转换为 Gmail?【英文标题】:How to convert raw emails (MIME) from AWS SES to Gmail? 【发布时间】:2017-05-23 13:28:46 【问题描述】:

我有一个与我的域帐户相关联的 gmail 帐户。

AWS SES 会将消息发送到我的 S3 存储桶。从那里,SNS 会以原始格式将消息转发到我的 gmail 地址。

如何自动将原始邮件转换为标准电子邮件格式?

【问题讨论】:

您可能需要检查Amazon SES FAQs 看看是否有帮助。它提到了用于发送电子邮件的可用 API 操作,SendEmail APISendRawEmail API,它们对实际电子邮件的组成提供不同级别的控制。 【参考方案1】:

原始消息采用标准电子邮件格式。我认为您想知道的是如何将标准原始电子邮件解析为您可以操作的对象,以便您可以将其转发给自己并让它看起来像标准电子邮件。 AWS 提供了一个教程,介绍如何通过 SES 使用 lambda 函数转发电子邮件,方法是首先将它们存储在您的 S3 存储桶中:https://aws.amazon.com/blogs/messaging-and-targeting/forward-incoming-email-to-an-external-destination/

如果您按照这些说明进行操作,您会发现收到的电子邮件是以附件形式出现的,而不是看起来像标准电子邮件。以下代码是对 AWS 提供的 Python 代码的更改,可以满足您的需求(将其替换为教程中提供的代码):

# Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# Altered from original by Adam Winter
#
# This file is licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License. A copy of the
# License is located at
#
# http://aws.amazon.com/apache2.0/
#
# This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
# OF ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.

import os
import boto3
import email
import re
import html
from botocore.exceptions import ClientError
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
from email.mime.image import MIMEImage

region = os.environ['Region']

def get_message_from_s3(message_id):

    incoming_email_bucket = os.environ['MailS3Bucket']
    incoming_email_prefix = os.environ['MailS3Prefix']

    if incoming_email_prefix:
        object_path = (incoming_email_prefix + "/" + message_id)
    else:
        object_path = message_id

    object_http_path = (f"http://s3.console.aws.amazon.com/s3/object/incoming_email_bucket/object_path?region=region")

    # Create a new S3 client.
    client_s3 = boto3.client("s3")

    # Get the email object from the S3 bucket.
    object_s3 = client_s3.get_object(Bucket=incoming_email_bucket,
        Key=object_path)
    # Read the content of the message.
    file = object_s3['Body'].read()

    file_dict = 
        "file": file,
        "path": object_http_path
    

    return file_dict

def create_message(file_dict):

    stringMsg = file_dict['file'].decode('utf-8')

    # Create a MIME container.
    msg = MIMEMultipart('alternative')

    sender = os.environ['MailSender']
    recipient = os.environ['MailRecipient']

    # Parse the email body.
    mailobject = email.message_from_string(file_dict['file'].decode('utf-8'))
    #print(mailobject.as_string())

    # Get original sender for reply-to
    from_original = mailobject['Return-Path']
    from_original = from_original.replace('<', '');
    from_original = from_original.replace('>', '');
    print(from_original)

    # Create a new subject line.
    subject = mailobject['Subject']
    print(subject)

    if mailobject.is_multipart():

        index = stringMsg.find('Content-Type: multipart/')
        stringBody = stringMsg[index:]
        #print(stringBody)
        stringData = 'Subject: ' + subject + '\nTo: ' + sender + '\nreply-to: ' + from_original + '\n' + stringBody

        message = 
            "Source": sender,
            "Destinations": recipient,
            "Data": stringData
        
        return message

        for part in mailobject.walk():
            ctype = part.get_content_type()
            cdispo = str(part.get('Content-Disposition'))

            # case for each common content type
            if ctype == 'text/plain' and 'attachment' not in cdispo:
                bodyPart = MIMEText(part.get_payload(decode=True), 'plain', part.get_content_charset())
                msg.attach(bodyPart)

            if ctype == 'text/html' and 'attachment' not in cdispo:
                mt = MIMEText(part.get_payload(decode=True), 'html', part.get_content_charset())
                email.encoders.encode_quopri(mt)
                del mt['Content-Transfer-Encoding']
                mt.add_header('Content-Transfer-Encoding', 'quoted-printable')
                msg.attach(mt)

            if 'attachment' in cdispo and 'image' in ctype:
                mi = MIMEImage(part.get_payload(decode=True), ctype.replace('image/', ''))
                del mi['Content-Type']
                del mi['Content-Disposition']
                mi.add_header('Content-Type', ctype)
                mi.add_header('Content-Disposition', cdispo)
                msg.attach(mi)

            if 'attachment' in cdispo and 'application' in ctype:
                ma = MIMEApplication(part.get_payload(decode=True), ctype.replace('application/', ''))
                del ma['Content-Type']
                del ma['Content-Disposition']
                ma.add_header('Content-Type', ctype)
                ma.add_header('Content-Disposition', cdispo)
                msg.attach(ma)


    # not multipart - i.e. plain text, no attachments, keeping fingers crossed
    else:
        body = MIMEText(mailobject.get_payload(decode=True), 'UTF-8')
        msg.attach(body)

    # The file name to use for the attached message. Uses regex to remove all
    # non-alphanumeric characters, and appends a file extension.
    filename = re.sub('[^0-9a-zA-Z]+', '_', subject_original)

    # Add subject, from and to lines.
    msg['Subject'] = subject
    msg['From'] = sender
    msg['To'] = recipient
    msg['reply-to'] = mailobject['Return-Path']

    # Create a new MIME object.
    att = MIMEApplication(file_dict["file"], filename)
    att.add_header("Content-Disposition", 'attachment', filename=filename)

    # Attach the file object to the message.
    msg.attach(att)
    message = 
        "Source": sender,
        "Destinations": recipient,
        "Data": msg.as_string()
    
    return message

def send_email(message):
    aws_region = os.environ['Region']
# Create a new SES client.
    client_ses = boto3.client('ses', region)
    # Send the email.
    try:
        #Provide the contents of the email.
        response = client_ses.send_raw_email(
            Source=message['Source'],
            Destinations=[
                message['Destinations']
            ],
            RawMessage=
                'Data':message['Data']
            
        )

    # Display an error if something goes wrong.
    except ClientError as e:
        print('send email ClientError Exception')
        output = e.response['Error']['Message']
    else:
        output = "Email sent! Message ID: " + response['MessageId']

    return output

def lambda_handler(event, context):
    # Get the unique ID of the message. This corresponds to the name of the file
    # in S3.
    message_id = event['Records'][0]['ses']['mail']['messageId']
    print(f"Received message ID message_id")

    # Retrieve the file from the S3 bucket.
    file_dict = get_message_from_s3(message_id)

    # Create the message.
    message = create_message(file_dict)

    # Send the email and print the result.
    result = send_email(message)
    print(result)

【讨论】:

【参考方案2】:

对于那些收到此错误的人:

'bytes' object has no attribute 'encode'

在这一行:

body = MIMEText(mailobject.get_payload(decode=True), 'UTF-8')

我可以让它工作。我不是这方面的专家,因此代码可能需要一些改进。电子邮件正文还包括 html 标签。但至少它已经交付了。

如果解码电子邮件仍然失败,错误消息将出现在您的 CloudWatch 日志中。您还将收到一封包含错误消息的电子邮件。

payload = mailobject.get_payload(decode=True)
try:
    decodedPayload = payload.decode()
    body = MIMEText(decodedPayload, 'UTF-8')
    msg.attach(body)
except Exception as error:
    errorMsg = "An error occured when decoding the email payload:\n" + str(error)
    print(errorMsg)
    body = errorMsg + "\nPlease download it manually from the S3 bucket."
    msg.attach(MIMEText(body, 'plain'))

您可以自行决定将哪些信息添加到错误电子邮件中,例如主题或发件人地址。

另一个提示:使用上面的代码你会得到一个错误,因为subject_original 是未定义的。只需删除以下行。

# The file name to use for the attached message. Uses regex to remove all
# non-alphanumeric characters, and appends a file extension.
filename = re.sub('[^0-9a-zA-Z]+', '_', subject_original)

# Create a new MIME object.
att = MIMEApplication(file_dict["file"], filename)
att.add_header("Content-Disposition", 'attachment', filename=filename)

# Attach the file object to the message.
msg.attach(att)

据我了解,此代码应该将原始电子邮件添加为附件,这不是我想要的。

【讨论】:

以上是关于如何将原始电子邮件 (MIME) 从 AWS SES 转换为 Gmail?的主要内容,如果未能解决你的问题,请参考以下文章

验证/测试 mime4j 解析内容的输出

如何在java中解析原始mime内容?

到 MailMessage 的纯文本 mime 电子邮件

base64的MIME

将 MIME 中的附加数据嵌入到电子邮件的 HTML 部分。未链接到附件

从 MIME 消息信息创建 EML 文件