端對端範例

本文說明如何在 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>

您可以為電子郵件的 head 新增其中一種支援的格式 (JSON-LD微資料) 結構化資料,藉此定義餐廳並新增 OneClickAction。Gmail 支援 OneClickAction,並顯示特定 UI,讓使用者可以在收件匣中確認訂閱。

將下列標記複製到名為 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)

與使用者的 SubscriptionHandlerclass 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 中。如此一來,系統就會將 HTTP 200 回應代碼傳回 Gmail,以指出要求成功。如果要求中不包含必填欄位,要求處理常式會傳回 HTTP 400 回應代碼,表示要求無效。

系統會使用向應用程式根網址發出的 GET 要求,列出已收集到的訂閱項目。要求處理常式會先從 Datastore 擷取所有訂閱,然後透過簡單的計數器在頁面中輸出這些訂閱項目。

測試應用程式

將應用程式部署至 App Engine,然後前往 https://APP-ID.appspot.com/email (將 APP-ID 替換成您的 App Engine 應用程式 ID),將加註的電子郵件傳送給自己。

Gmail 中的動作

部署應用程式並插入部分訂閱項目後,請前往 https://APP-ID.appspot.com 造訪您的應用程式,取得訂閱項目摘要頁面