适用于 Python 的邮件 API

本页面介绍如何将 Mail API(一种旧版捆绑服务)与标准环境中的 Python 运行时搭配使用。您的应用可以通过 Python 版 App Engine 服务 SDK 访问捆绑服务。

概览

在 Python 中,邮件处理功能包含在 google.appengine.api.mail 模块中。这与 Python 2 不同,在 Python 2 中,mail_handlers 模块由 webapp 提供。适用于 Python 的邮件 API 可用于接收电子邮件和退信通知。

使用邮件 API

当电子邮件作为 HTTP POST 请求中的请求正文发送时,您的应用会收到邮件。为了让应用处理传入的电子邮件,应用需要将网址与路径 /_ah/mail/[ADDRESS] 进行匹配。路径的 [ADDRESS] 部分通常是带有后缀 @<Cloud-Project-ID>.appspotmail.com 的电子邮件地址。以此格式发送到应用的电子邮件将路由到函数。

Python 不需要应用在 app.yaml 文件中指定处理程序脚本,因此您可以移除 app.yaml 中的所有 handler 部分。

您的 app.yaml 文件应保留以下行:

inbound_services:
- mail
- mail_bounce

发送邮件

升级到 Python 时,您无需更改应用的配置。用于发送邮件的行为、功能和设置说明保持不变。如需了解详情,请参阅以下指南:

接收邮件

要接收邮件,您需要导入 google.appengine.api.mail 模块并使用 InboundEmailMessage 类来表示电子邮件。该类需要实例化才能从传入的 HTTP 请求中检索电子邮件内容。

以前,在 Python 2 中,应用可以通过替换 webapp 处理程序 InboundEmailHandler 中的 receive() 方法来访问 InboundEmailMessage 类。在 Python 中不需要如此操作;相反,应用需要实例化新对象。

Web 框架

使用 Python 网络框架时,InboundEmailMessage 构造函数会接收 HTTP 请求正文的字节。您可以使用多种方法在 Python 中创建 InboundEmailMessage 对象。以下是 Flask 和 Djago 应用的示例:

Python 3 (Flask)

在 Flask 中,request.get_data() 提供请求字节数。

@app.route("/_ah/bounce", methods=["POST"])
def receive_bounce():
    bounce_message = mail.BounceNotification(dict(request.form.lists()))

    # Do something with the message
    print("Bounce original: ", bounce_message.original)
    print("Bounce notification: ", bounce_message.notification)

    return "OK", 200

Python 3 (Django)

在 Django 中,request.body 提供 HTTP 请求正文的字节。

def receive_mail(request):
    message = mail.InboundEmailMessage(request.body)

    print(f"Received greeting for {message.to} at {message.date} from {message.sender}")
    for _, payload in message.bodies("text/plain"):
        print(f"Text/plain body: {payload.decode()}")
        break

    return HttpResponse("OK")

如需查看本指南中的完整代码示例,请参阅 GitHub

其他与 WSGI 兼容的框架

对于其他与 WSGI 兼容的框架,我们建议使用与 Flask 和 Django 示例相同的方法来创建 InboundEmailMessage。当 HTTP 请求正文的字节直接可用时,此方法有效。

没有网络框架的 WSGI 应用

如果您的应用是不使用网络框架的 WSGI 应用,则 HTTP 请求正文的字节可能无法直接可用。如果 HTTP 请求正文的字节直接可用,我们建议您使用 Python 网络框架。

在 Python 中,为 InboundEmailMessage 定义了一个名为 from_environ 的 factory 方法。此方法是一个将 WSGI environ 字典作为输入的类方法,可用于任何 WSGI 应用。

在以下示例中,请注意如何将 environ 作为输入来获取 mail_message

Python 3(WSGI 应用)

def HelloReceiver(environ, start_response):
    if environ["REQUEST_METHOD"] != "POST":
        return ("", http.HTTPStatus.METHOD_NOT_ALLOWED, [("Allow", "POST")])

    message = mail.InboundEmailMessage.from_environ(environ)

    print(f"Received greeting for {message.to} at {message.date} from {message.sender}")
    for content_type, payload in message.bodies("text/plain"):
        print(f"Text/plain body: {payload.decode()}")
        break

    response = http.HTTPStatus.OK
    start_response(f"{response.value} {response.phrase}", [])
    return ["success".encode("utf-8")]

接收退信通知

退信通知是来自电子邮件系统的自动邮件,表示应用的邮件递送出现问题。要处理退信通知,您的应用需要将传入的网址路径与 /_ah/bounce 路径相匹配。

InboundEmailMessage 类似,可通过替换 webapp 处理程序 BounceNotificationHandler 中的 receive() 方法来访问 Python 2 的 BounceNotification 类。

在 Python 中,应用需要实例化 BounceNotification 对象,该对象可以通过多种方式创建,具体取决于所使用的 Python 网络框架。

Web 框架

BounceNotification 对象使用通过调用 post_vars.get(key) 检索的值进行初始化。

使用 Python 网络框架(如 Flask 或 Django)时,BounceNotification 构造函数采用名为 post_vars 的字典,其中包含表单数据的 POST 请求。要检索数据,需要在输入对象上定义 get() 方法。keyBounceNotification 可以从中读取和检索的输入值列表,可以是以下任何一种:

original-to, original-cc, original-bcc, original-subject, original-text, notification-from, notification-to, notification-cc, notification-bcc, notification-subject, notification-text, raw-message

在大多数网络框架中,此数据以多字典形式在请求对象中提供。其中大多数类型都可以转换为由字符串键控的字典。

对于除 raw-message 之外的所有键,该值可以是任何值。通常,该值可以是单个值(如字符串),也可以是值列表(如 {'to': ['bob@example.com', 'alice@example.com']})。所有字段的默认值都是空字符串。这些值将填充 originalnotification 属性。

对于 raw-message 键,该值必须是 EmailMessage 构造函数的有效输入。它可以是单个值,也可以是单个值列表。raw-message 键用于初始化对象的 original_raw_message 属性。

Python 2 (webapp2)

class LogBounceHandler(BounceNotificationHandler):
    def receive(self, bounce_message):
        logging.info('Received bounce post ... [%s]', self.request)
        logging.info('Bounce original: %s', bounce_message.original)
        logging.info('Bounce notification: %s', bounce_message.notification)

Python 3 (Flask)

在 Flask 中,类型为 werkzeug.datastructures.MultiDictrequest.form 提供 POST 变量。但是,即使键存在多个值,此类型的 get() 方法也只会返回一个值。

为了获取与键对应的所有值,应用需要调用 dict(request.form.lists()),这会生成一个字典,其中每个值都是一个列表。

@app.route("/_ah/bounce", methods=["POST"])
def receive_bounce():
    bounce_message = mail.BounceNotification(dict(request.form.lists()))

    # Do something with the message
    print("Bounce original: ", bounce_message.original)
    print("Bounce notification: ", bounce_message.notification)

    return "OK", 200

Python 3 (Django)

在 Django 中,类型为 django.http.QueryDictrequest.POST 提供 POST 变量。但是,即使键存在多个值,此类型的 get() 方法也只会返回一个值。

为了获取与键对应的所有值,应用需要调用 dict(request.POST.lists()),这会生成一个字典,其中每个值都是一个列表。

def receive_bounce(request):
    bounce_message = mail.BounceNotification(dict(request.POST.lists()))

    # Do something with the message
    print(f"Bounce original: {bounce_message.original}")
    print(f"Bounce notification: {bounce_message.notification}")

    return HttpResponse("OK")

没有网络框架的 WSGI 应用

如果您的应用是不使用网络框架的 WSGI 应用,则 HTTP POST 请求的表单变量可能无法在字典中直接可用。

在 Python 中,为 BounceNotification 定义了一个名为 from_environ 的 factory 方法。此方法是一个将 WSGI environ 字典作为输入的类方法,可用于任何 WSGI 应用。

在以下示例中,请注意如何将 environ 作为输入来获取 bounce_message

Python 3

def BounceReceiver(environ, start_response):
    if environ["REQUEST_METHOD"] != "POST":
        return ("", http.HTTPStatus.METHOD_NOT_ALLOWED, [("Allow", "POST")])

    bounce_message = mail.BounceNotification.from_environ(environ)

    # Do something with the message
    print("Bounce original: ", bounce_message.original)
    print("Bounce notification: ", bounce_message.notification)

    # Return suitable response
    response = http.HTTPStatus.OK
    start_response(f"{response.value} {response.phrase}", [])
    return ["success".encode("utf-8")]

代码示例

如需查看本指南中的完整代码示例,请参阅 GitHub