End-to-end example

This article shows how to build an App Engine app in Python that sends annotated emails to users asking to confirm a mailing list subscription directly from their inbox and collects the subscriptions in the Datastore.

Prerequisites and project setup

This guide assumes you have already installed the App Engine SDK and know how to create, run, and publish App Engine projects.

First, create a directory for your project. Put all of the files for your application in this directory.

Copy the following code to a file named app.yaml and replace the {{ APPID }} placeholder with your unique App Engine app id:

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

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

libraries: - 
name: jinja2
  version: latest 

Create a file named main.py in your App Engine project folder and copy the following code to setup the handlers for collecting and listing subscriptions, and for sending annotated emails:

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

Adding structured data to the email

Let's start with a very simple email asking the user to confirm a mailing list subscription:

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

You can add structured data in one of the supported formats ( JSON-LD or Microdata ) to the head of the email to define the restaurant and add a OneClickAction . Gmail supports the OneClickAction and shows a specific UI to users to allow them to confirm their subscription from their inbox.

Copy the following markup into a file named mail_template.html :

JSON-LD

 < h 
 t 
 ml 
>  
< head 
>  
< t 
 i 
 tle 
> Please 
  
 co 
 nf 
 irm 
  
 your 
  
 subscrip 
 t 
 io 
 n 
  
 t 
 o 
  
 Maili 
 n 
 g 
 - 
 Lis 
 t 
  
 XYZ?</ 
 t 
 i 
 tle 
>  
< /head 
>  
< body 
>  
< scrip 
 t 
  
 t 
 ype= 
 "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" 
  
 } 
  
< /scrip 
 t 
>  
< p 
>  
 Dear 
  
 Joh 
 n 
 , 
  
 please 
  
 co 
 nf 
 irm 
  
 t 
 ha 
 t 
  
 you 
  
 wish 
  
 t 
 o 
  
 be 
  
 subscribed 
  
 t 
 o 
  
 t 
 he 
  
 maili 
 n 
 g 
  
 lis 
 t 
  
 XYZ. 
  
< /p 
>  
< /body 
>
< /h 
 t 
 ml 
> 

Microdata

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

The structured data above describes a mailing list called "XYZ" and a ConfirmAction . The handler for the action is a HttpActionHandler that sends POST requests to the URL specified in the url property.

Sending subscription requests to the users

Copy the following code into a file named emailsender.py in the App Engine project folder:

  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 
 ) 
 

The EmailSender class requires the user to be logged-in so that their email address can be retrieved. Then, it loads the email body from mail_template.html , replaces the confirm_url placeholder in it with the root url of the App Engine app ( https://APP-ID.appspot.com ), and sends the email to the currently logged-in user as themselves.

Collecting and listing subscriptions

Copy the following code into a file named subscribe.py in the App Engine project folder:

  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 
 ) 
 

The SubscribeHandler class listens to both POST and GET requests sent to the app root url ( https://APP-ID.appspot.com ). POST requests are used by Gmail to insert new subscriptions including the user_id` parameter that corresponds to the user, as in the following example:

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

The request handler simply checks that the required user_id is defined and then stores the subscription in the Datastore . This results in a HTTP 200 response code being sent back to Gmail to signal the successful request. In case the request doesn't include the required field, the request handler will return a HTTP 400 response code, signaling the invalid request.

GET requests to the app root url are used to list the subscriptions that have been collected. The request handler first fetches all subscriptions from the Datastore and then prints them in the page, together with a simple counter.

Testing the app

Deploy your app to App Engine and visit https://APP-ID.appspot.com/email (replace APP-ID with your App Engine app id) to send the annotated email to yourself.

Actions in Gmail

Once you have deployed your app and inserted some subscriptions, visit your app at https://APP-ID.appspot.com to get a page summarizing the subscriptions

Design a Mobile Site
View Site in Mobile | Classic
Share by: