Mail API for Python

Note: To improve email security and ensure reliable, high-volume email delivery,we recommend that youmigrate from the legacy Mail API to an SMTP-based emailservice, such asSendGrid, Mailgun, or Mailjet.

This page describes how to use the Mail API, one of the legacy bundled services,with thePython runtime forthe standard environment. Your app can access the bundled servicesthrough theApp Engine services SDK for Python.

Overview

In Python, the mail handling functionality is included in thegoogle.appengine.api.mail module. This is different from Python 2, where themail_handlers module was provided by webapp. The MailAPI for Python can be used to receive emails and bounce notifications.

Using the Mail API

Your app receives mail when an email is sent as the request body in an HTTP POSTrequest. For the app to handle incoming emails, the app needs to match the URLwith the path/_ah/mail/[ADDRESS]. The[ADDRESS] part ofthe path is usually an email address with the suffix@<Cloud-Project-ID>.appspotmail.com. Emails sent to the app in this formatwill be routed to the function.

Python doesn't require the app to specify a handler script in theapp.yamlfile, so you can remove allhandler sections inapp.yaml.

Yourapp.yaml file should keep the following lines:

inbound_services:-mail-mail_bounce

Sending mail

You do not need to make changes to your app's configuration when upgrading toPython. The behavior, features, and setup instructions for sending mailremains the same. Refer to the following guides for more details:

Receiving mail

To receive mail, you need to import thegoogle.appengine.api.mail module anduse theInboundEmailMessage class to represent an email. This class needs tobe instantiated to retrieve the email content from the incoming HTTP request.

Previously in Python 2, apps could access theInboundEmailMessage class byoverriding thereceive() method in the webapp handlerInboundEmailHandler.This is not needed in Python; instead, the app needs to instantiate a new object.

Web frameworks

When using Python web frameworks, theInboundEmailMessage constructor takes inthe bytes of the HTTP request body. There are multiple ways to create theInboundEmailMessage object in Python. The following are examples for Flaskand Djago apps:

Python 3 (Flask)

In Flask,request.get_data() gives the request bytes.

@app.route("/_ah/mail/<path>",methods=["POST"])defreceive_mail(path):message=mail.InboundEmailMessage(request.get_data())# Do something with the messageprint(f"Received greeting for{escape(message.to)} at{escape(message.date)} from{escape(message.sender)}")forcontent_type,payloadinmessage.bodies("text/plain"):print(f"Text/plain body:{payload.decode()}")breakreturn"OK",200

Python 3 (Django)

In Django,request.body gives the bytes of the HTTP request body.

defreceive_mail(request):message=mail.InboundEmailMessage(request.body)print(f"Received greeting for{message.to} at{message.date} from{message.sender}")for_,payloadinmessage.bodies("text/plain"):print(f"Text/plain body:{payload.decode()}")breakreturnHttpResponse("OK")

To view the complete code samples from this guide, seeGitHub.

Other WSGI-compliant frameworks

For other WSGI-compliant frameworks, we recommend using the same method as theFlask and Django examples to create theInboundEmailMessage. This method workswhen the bytes of the HTTP request body are directly available.

WSGI app without a web framework

If your app is a WSGI app that does not use a web framework, it is possible thatthe bytes of the HTTP request body is not directly available. If the bytes ofthe HTTP request body are directly available, we recommend that you use a Pythonweb framework.

In Python, a factory method namedfrom_environ is defined forInboundEmailMessage. This method is a class method that takes theWSGIenviron dictionaryas the input, and can be used for any WSGI application.

In the following example, notice howenviron is taken in as an input to get themail_message:

Python 3 (WSGI app)

defHelloReceiver(environ,start_response):ifenviron["REQUEST_METHOD"]!="POST":return("",http.HTTPStatus.METHOD_NOT_ALLOWED,[("Allow","POST")])message=mail.InboundEmailMessage.from_environ(environ)print(f"Received greeting for{message.to} at{message.date} from{message.sender}")forcontent_type,payloadinmessage.bodies("text/plain"):print(f"Text/plain body:{payload.decode()}")breakresponse=http.HTTPStatus.OKstart_response(f"{response.value}{response.phrase}",[])return["success".encode("utf-8")]

Receiving bounce notifications

A bounce notification is an automated message from an email system thatindicates a problem with your app's message delivery. To process bouncenotifications, your app needs to match incoming URL paths with the/_ah/bouncepath.

LikeInboundEmailMessage, theBounceNotification class for Python 2 wasaccessible by overriding thereceive() method in the webapp handlerBounceNotificationHandler.

In Python, the app needs to instantiate theBounceNotification object,which can be created in multiple ways depending on the Python web framework used.

Web frameworks

TheBounceNotification object is initialized with the values that areretrieved by callingpost_vars.get(key).

When using a Python web framework, like Flask or Django, theBounceNotification constructor takes in a dictionary namedpost_vars, whichcontains the POST request of the form data. To retrieve the data, the methodget() needs to be defined on the input object. Thekey is a list of inputvalues that can be read and retrieved byBounceNotification and can be any ofthe following:

original-to, original-cc, original-bcc, original-subject, original-text, notification-from, notification-to, notification-cc, notification-bcc, notification-subject, notification-text, raw-message

In most web frameworks, this data is available as a multi dictionary in therequest object. Most of these types can be converted into a dictionary keyed bystrings.

For all keys exceptraw-message, the value can be anything. Usually, the value iseither a single value such as a string, or a list of values, such as{'to': ['bob@example.com', 'alice@example.com']}. The default value for all fields isan empty string. These values will populate theoriginal andnotificationproperties.

For theraw-message key, the value needs to be a valid input to the constructor ofEmailMessage.It can either be a single value or a single valued list. Theraw-message keyis used to initialize theoriginal_raw_message property of the object.

Python 2 (webapp2)

classLogBounceHandler(BounceNotificationHandler):defreceive(self,bounce_message):logging.info('Received bounce post ... [%s]',self.request)logging.info('Bounce original:%s',bounce_message.original)logging.info('Bounce notification:%s',bounce_message.notification)

Python 3 (Flask)

In Flask,request.formof typewerkzeug.datastructures.MultiDictgives the POST variables. However, theget() method for this type only returnsa value, even if there are multiple values for the key is present.

To get all values corresponding to a key,the app needs to calldict(request.form.lists()),which results in a dictionary where each value is a list.

@app.route("/_ah/bounce",methods=["POST"])defreceive_bounce():bounce_message=mail.BounceNotification(dict(request.form.lists()))# Do something with the messageprint("Bounce original: ",bounce_message.original)print("Bounce notification: ",bounce_message.notification)return"OK",200

Python 3 (Django)

In Django,request.POSTof typedjango.http.QueryDictgives the POST variables. However, theget() method for this type only returnsa value, even if there are multiple values for the key is present.

To get all values corresponding to a key,the app needs to calldict(request.POST.lists()),which results in a dictionary where each value is a list.

defreceive_bounce(request):bounce_message=mail.BounceNotification(dict(request.POST.lists()))# Do something with the messageprint(f"Bounce original:{bounce_message.original}")print(f"Bounce notification:{bounce_message.notification}")returnHttpResponse("OK")
Note: To maintain similar behavior as Python 2, userequest.form (in Flask)orrequest.POST (in Django) to initialize each key inBounceNotification toa single value even if there are multiple values present. To populateBounceNotification with multiple values for each key from a BounceNotification,dict(request.form.list()) for Flask ordict(request.POST.list()) for Django can be passed in as an argument. Theto,cc, andbcc fields will be initialized to strings or list of strings,depending on the input dict.

WSGI app without a web framework

If your app is a WSGI app that does not use a web framework, it is possible thatthe form variables of the HTTP POST request are not directly available in a dictionary.

In Python, a factory method namedfrom_environ is defined forBounceNotification. This method is a class method that takes the WSGIenviron dictionary as the input, and can be used for any WSGI application.

In the following example, notice howenviron is taken in as an input to get thebounce_message:

Python 3

defBounceReceiver(environ,start_response):ifenviron["REQUEST_METHOD"]!="POST":return("",http.HTTPStatus.METHOD_NOT_ALLOWED,[("Allow","POST")])bounce_message=mail.BounceNotification.from_environ(environ)# Do something with the messageprint("Bounce original: ",bounce_message.original)print("Bounce notification: ",bounce_message.notification)# Return suitable responseresponse=http.HTTPStatus.OKstart_response(f"{response.value}{response.phrase}",[])return["success".encode("utf-8")]

Code samples

To view the complete code samples from this guide, seeGitHub.

Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.

Last updated 2025-12-15 UTC.