En este artículo, se muestra cómo compilar una app de App Engine en Python que envía correos electrónicos con anotaciones a los usuarios para solicitarles que confirmen una suscripción a una lista de distribución directamente desde su carpeta Recibidos y recopila las suscripciones en Datastore.
Requisitos previos y configuración del proyecto
En esta guía, se supone que ya instalaste el SDK de App Engine y que sabes cómo crear, ejecutar y publicar proyectos de App Engine.
Primero, crea un directorio para tu proyecto. Coloca todos los archivos de tu aplicación en este directorio.
Copia el siguiente código en un archivo llamado app.yaml
y reemplaza el marcador de posición {{ APPID }}
por el ID único de tu app de App Engine:
application: {{ APPID }}
version: 1
runtime: python27
api_version: 1
threadsafe: true
handlers:
- url: /.*
script: main.app
libraries:
- name: jinja2
version: latest
Crea un archivo llamado main.py
en la carpeta de tu proyecto de App Engine y copia el siguiente código para configurar los controladores para recopilar y enumerar suscripciones, y para enviar correos electrónicos con anotaciones:
import webapp2
from emailsender import EmailSender
from subscribe import SubscribeHandler
app = webapp2.WSGIApplication([('/', SubscribeHandler), ('/email', EmailSender)], debug=True)
Cómo agregar datos estructurados al correo electrónico
Comencemos con un correo electrónico muy simple en el que se le pide al usuario que confirme la suscripción a una lista de distribución:
<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>
Puedes agregar datos estructurados en uno de los formatos admitidos (JSON-LD o microdatos) al head
del correo electrónico para definir el restaurante y agregar un OneClickAction. Gmail admite OneClickAction
y muestra una IU específica a los usuarios para permitirles confirmar su suscripción desde la carpeta Recibidos.
Copia el siguiente lenguaje de marcado en un archivo llamado 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>
Microdatos
<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>
Los datos estructurados anteriores describen una lista de distribución de correo electrónico llamada "XYZ" y un ConfirmAction
. El controlador de la acción es un HttpActionHandler
que envía solicitudes POST a la URL especificada en la propiedad url
.
Envío de solicitudes de suscripción a los usuarios
Copia el siguiente código en un archivo llamado emailsender.py
en la carpeta del proyecto de 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)
La clase EmailSender
requiere que el usuario acceda a su cuenta para que se pueda recuperar su dirección de correo electrónico. Luego, carga el cuerpo del correo electrónico desde mail_template.html
, reemplaza el marcador de posición confirm_url
en él por la URL raíz de la app de App Engine (https://APP-ID.appspot.com
) y envía el correo electrónico al usuario que accedió actualmente como si fuera él mismo.
Cómo recopilar y enumerar suscripciones
Copia el siguiente código en un archivo llamado subscribe.py
en la carpeta del proyecto de 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)
El parámetro 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` que corresponde al usuario, como en el siguiente ejemplo:
https://subscribe.appspot.com/?user_id=123abcd
El controlador de solicitudes simplemente verifica que se haya definido el user_id requerido y, luego, almacena la suscripción en Datastore. Esto genera que se envíe un código de respuesta HTTP 200
a Gmail para indicar que la solicitud se completó correctamente. En caso de que la solicitud no incluya el campo obligatorio, el controlador de solicitudes devolverá un código de respuesta HTTP 400
, lo que indicará que la solicitud no es válida.
Las solicitudes GET
a la URL raíz de la app se usan para enumerar las suscripciones que se recopilaron. Primero, el controlador de solicitudes recupera todas las suscripciones de Datastore y, luego, las imprime en la página junto con un contador simple.
Prueba la app
Implementa tu app en App Engine y visita https://APP-ID.appspot.com/email
(reemplaza APP-ID
por el ID de tu app de App Engine) para enviarte el correo electrónico con anotaciones.
Una vez que implementes tu app y agregues algunas suscripciones, visita tu app en https://APP-ID.appspot.com
para obtener una página que resuma las suscripciones.