端到端示例

本文介绍了如何使用 Python 构建一个 App Engine 应用,该应用会向用户发送带注释的电子邮件,要求用户直接从收件箱确认邮寄名单订阅,并在 Datastore 中收集订阅。

前提条件和项目设置

本指南假定您已安装 App Engine SDK,并且知道如何创建、运行和发布 App Engine 项目。

首先,为您的项目创建一个目录。将应用的所有文件放入此目录中。

将以下代码复制到名为 app.yaml 的文件中,并将 {{ APPID }} 占位符替换为您的唯一 App Engine 应用 ID:

application: {{ APPID }}
version: 1
runtime: python27
api_version: 1
threadsafe: true

handlers:
- url: /.*
  script: main.app

libraries:
- name: jinja2
  version: latest

在 App Engine 项目文件夹中创建一个名为 main.py 的文件,然后复制以下代码以设置用于收集和列出订阅以及发送带注释电子邮件的处理脚本:

import webapp2

from emailsender import EmailSender
from subscribe import SubscribeHandler

app = webapp2.WSGIApplication([('/', SubscribeHandler), ('/email', EmailSender)], debug=True)

向电子邮件添加结构化数据

我们先从一封非常简单的电子邮件开始,要求用户确认邮寄名单订阅:

<html>
  <head>
    <title>Please confirm your subscription to Mailing-List XYZ?</title>
  </head>
  <body>
    <p>
      Dear John, please confirm that you wish to be subscribed to the
      mailing list XYZ
    </p>
  </body>
</html>

您可以将结构化数据(采用某种受支持的格式 [JSON-LD微数据])添加到电子邮件的 head,以定义餐厅并添加 OneClickAction。Gmail 支持 OneClickAction,并向用户显示特定界面,以便用户在收件箱中确认订阅。

将以下标记复制到名为 mail_template.html 的文件中:

JSON-LD

<html>
  <head>
  <title>Please confirm your subscription to Mailing-List XYZ?</title>
  </head>
  <body>
    <script type="application/ld+json">
    {
      "@context": "http://schema.org",
      "@type": "EmailMessage",
      "potentialAction": {
        "@type": "ConfirmAction",
        "name": "Confirm Subscription",
        "handler": {
          "@type": "HttpActionHandler",
          "url": "{{ confirm_url }}",
          "method": "http://schema.org/HttpRequestMethod/POST",
        }
      },
      "description": "Confirm subscription to mailing list XYZ"
    }
    </script>
    <p>
      Dear John, please confirm that you wish to be subscribed to the mailing list XYZ.
    </p>
  </body>
</html>

微数据

<html>
  <head>
    <title>Please confirm your subscription to Mailing-List XYZ?</title>
  </head>
  <body>
    <div itemscope itemtype="http://schema.org/EmailMessage">
      <div itemprop="potentialAction" itemscope itemtype="http://schema.org/ConfirmAction">
        <meta itemprop="name" content="Approve Expense"/>
        <div itemprop="handler" itemscope itemtype="http://schema.org/HttpActionHandler">
          <link itemprop="url" href="https://myexpenses.com/approve?expenseId=abc123"/>
          <meta itemprop="url" content="{{ confirm_url }}"/>
          <link itemprop="method" href="http://schema.org/HttpRequestMethod/POST"/>
        </div>
      </div>
      <meta itemprop="description" content="Approval request for John's $10.13 expense for office supplies"/>
    </div>
    <p>
      Dear John, please confirm that you wish to be subscribed to the mailing list XYZ.
    </p>
  </body>
</html>

上述结构化数据描述了名为“XYZ”的邮寄名单和 ConfirmAction。该操作的处理脚本是 HttpActionHandler,用于向 url 属性中指定的网址发送 POST 请求。

向用户发送订阅请求

将以下代码复制到 App Engine 项目文件夹中名为 emailsender.py 的文件中:

import jinja2
import os
import webapp2

from google.appengine.api import mail
from google.appengine.api import users

from urlparse import urlparse

class EmailSender(webapp2.RequestHandler):

  def get(self):
    # require users to be logged in to send emails
    user = users.get_current_user()
    if not user:
      self.redirect(users.create_login_url(self.request.uri))
      return

    email = user.email()

    # The confirm url corresponds to the App Engine app url
    pr = urlparse(self.request.url)
    confirm_url = '%s://%s?user=%s' % (pr.scheme, pr.netloc, user.user_id())

    # load the email template and replace the placeholder with the confirm url
    jinja_environment = jinja2.Environment(
        loader=jinja2.FileSystemLoader(os.path.dirname(__file__)))
    template = jinja_environment.get_template('mail_template.html')
    email_body = template.render({'confirm_url': confirm_url})

    message = mail.EmailMessage(
        sender = email,
        to = email,
        subject = 'Please confirm your subscription to Mailing-List XYZ',
        html = email_body)

    try:
      message.send()
      self.response.write('OK')
    except:
      self.error(500)

EmailSender 类要求用户登录,以便检索其电子邮件地址。然后,它会从 mail_template.html 加载电子邮件正文,将其中的 confirm_url 占位符替换为 App Engine 应用的根网址 (https://APP-ID.appspot.com),并以当前登录用户的身份将电子邮件发送给该用户。

收集和列出订阅

将以下代码复制到 App Engine 项目文件夹中名为 subscribe.py 的文件中:

import webapp2

from emailsender import EmailSender
from google.appengine.ext import db


class SubscribeHandler(webapp2.RequestHandler):

  def post(self):
    user_id = self.request.get('user')

    # insert the subscription into the Datastore
    subscription = Subscription(user_id=user_id)
    subscription.put()

  def get(self):
    # retrieve up to 1000 subscriptions from the Datastore
    subscriptions = Subscription.all().fetch(1000)

    if not subscriptions:
      self.response.write('No subscriptions')
      return

    count = len(subscriptions)

    for s in subscriptions:
      self.response.write('%s subscribed<br/>' % (s.user_id))

    self.response.write('<br/>')
    self.response.write('%d subscriptions.' % (count))


class Subscription(db.Model):
    user_id = db.TextProperty(required=True)

与用户对应的 SubscribeHandlerclass listens to bothPOSTandGETrequests sent to the app root url (https://APP-ID.appspot.com).POSTrequests are used by Gmail to insert new subscriptions including theuser_id` 参数,如以下示例所示:

https://subscribe.appspot.com/?user_id=123abcd

请求处理程序只需检查是否已定义所需的 user_id,然后将订阅存储在 Datastore 中即可。这会导致系统向 Gmail 发送 HTTP 200 响应代码,以指示请求成功。如果请求不包含必需字段,请求处理程序将返回 HTTP 400 响应代码,表示请求无效。

对应用根网址的 GET 请求用于列出已收集的订阅。请求处理程序首先从 Datastore 提取所有订阅,然后在页面中将其与一个简单的计数器一起输出。

测试应用

将应用部署到 App Engine,然后访问 https://APP-ID.appspot.com/email(将 APP-ID 替换为您的 App Engine 应用 ID),将带注释的电子邮件发送给自己。

Gmail 中的操作

部署应用并插入一些订阅后,请访问 https://APP-ID.appspot.com 以查看一个页面,其中会汇总这些订阅