本文介绍如何在 Python 中构建一个 App Engine 应用,以便将带注释的电子邮件发送给用户,让其直接从收件箱中确认邮寄名单订阅,并收集数据存储区中的订阅。
前提条件和项目设置
本指南假定您已安装 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
,它会将 POST 请求发送到 url
属性中指定的网址。
向用户发送订阅请求
将以下代码复制到 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 both
POSTand
GETrequests sent to the app root url (
https://APP-ID.appspot.com).
POSTrequests are used by Gmail to insert new subscriptions including the
user_id` 参数,如以下示例所示:
https://subscribe.appspot.com/?user_id=123abcd
请求处理程序仅检查是否已定义所需的 user_id,然后将订阅存储在 Datastore 中。系统会将 HTTP 200
响应代码发送回 Gmail,以表明请求成功。如果请求不包含必填字段,则请求处理程序将返回 HTTP 400
响应代码,表明请求无效。
针对应用根网址的 GET
请求用于列出收集的订阅。请求处理程序首先从数据存储区提取所有订阅,然后将这些订阅连同一个简单的计数器输出到页面中。
测试应用
将您的应用部署到 App Engine 并访问 https://APP-ID.appspot.com/email
(将 APP-ID
替换为您的 App Engine 应用 ID),以将带有注释的电子邮件发送给您自己。
部署应用并插入一些订阅后,请访问位于 https://APP-ID.appspot.com
的应用,获取汇总订阅的页面