Exemplo completo

Este artigo mostra como criar um aplicativo do App Engine em Python que envia e-mails anotados para usuários pedindo para confirmar uma inscrição em uma lista de e-mails diretamente da caixa de entrada deles e coleta as inscrições no Datastore.

Pré-requisitos e configuração do projeto

Este guia pressupõe que você já tenha instalado o SDK do App Engine e saiba como criar, executar e publicar projetos do App Engine.

Primeiro, crie um diretório para o projeto. Coloque todos os arquivos do aplicativo nesse diretório.

Copie o código a seguir para um arquivo chamado app.yaml e substitua o marcador {{ APPID }} pelo ID exclusivo do aplicativo do App Engine:

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

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

libraries:
- name: jinja2
  version: latest

Crie um arquivo chamado main.py na pasta de projeto do App Engine e copie o código a seguir para configurar os gerenciadores para coletar e listar assinaturas e para enviar e-mails anotados:

import webapp2

from emailsender import EmailSender
from subscribe import SubscribeHandler

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

Adicionar dados estruturados ao e-mail

Vamos começar com um e-mail bem simples pedindo ao usuário para confirmar a inscrição de uma lista de e-mails:

<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>

É possível adicionar dados estruturados em um dos formatos aceitos (JSON-LD ou Microdados) ao head do e-mail para definir o restaurante e adicionar uma OneClickAction. O Gmail é compatível com o OneClickAction e mostra uma IU específica para que os usuários confirmem a assinatura na caixa de entrada.

Copie a seguinte marcação em um arquivo chamado 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>

Microdados

<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>

Os dados estruturados acima descrevem uma lista de e-mails chamada "XYZ" e um ConfirmAction. O gerenciador da ação é um HttpActionHandler que envia solicitações POST para o URL especificado na propriedade url.

Envio de solicitações de inscrição aos usuários

Copie o seguinte código em um arquivo chamado emailsender.py na pasta do projeto do App Engine:

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)

A classe EmailSender exige que o usuário faça login para que o endereço de e-mail possa ser recuperado. Em seguida, ele carrega o corpo do e-mail de mail_template.html, substitui o marcador confirm_url pelo URL raiz do aplicativo do App Engine (https://APP-ID.appspot.com) e envia o e-mail para o usuário conectado no momento como ele mesmo.

Como coletar e listar assinaturas

Copie o seguinte código em um arquivo chamado subscribe.py na pasta do projeto do App Engine:

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)

O parâmetro 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` que corresponde ao usuário, como no exemplo a seguir:

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

O gerenciador de solicitações apenas verifica se o user_id necessário está definido e, em seguida, armazena a assinatura no Datastore. Isso faz com que um código de resposta HTTP 200 seja enviado de volta ao Gmail para sinalizar que a solicitação foi bem-sucedida. Caso a solicitação não inclua o campo obrigatório, o gerenciador de solicitações retornará um código de resposta HTTP 400, sinalizando a solicitação inválida.

As solicitações GET para o URL raiz do app são usadas para listar as assinaturas que foram coletadas. O manipulador de solicitações primeiro busca todas as inscrições do armazenamento de dados e depois as exibe na página, junto com um contador simples.

Como testar o app

Implante seu aplicativo no App Engine e acesse https://APP-ID.appspot.com/email (substitua APP-ID pelo ID do aplicativo do App Engine) para enviar o e-mail anotado para você.

Ações no Gmail

Depois de implantar seu app e inserir algumas assinaturas, acesse-o em https://APP-ID.appspot.com para ver uma página com o resumo das assinaturas