API de correo electrónico para Python 3

En esta página, se describe cómo usar la API de Mail, uno de los servicios agrupados en paquetes heredados, con el entorno de ejecución de Python 3 para el entorno estándar. Tu aplicación puede acceder a los servicios en paquetes a través del SDK de servicios de App Engine para Python 3.

Descripción general

En Python 3, la función de control de correo electrónico se incluye en el módulo google.appengine.api.mail. Esto es diferente de Python 2, en el que aplicación web proporcionó el módulo mail_handlers. La API de correo electrónico para Python 3 se puede usar fin de recibir correos electrónicos y notificaciones de rebote.

Usa la API de correo electrónico

La app recibe correos electrónicos cuando se envía un correo electrónico como el cuerpo de la solicitud en una solicitud HTTP POST. Para que la app controle los correos electrónicos entrantes, debe coincidir la URL con la ruta de acceso /_ah/mail/[ADDRESS]. La parte [ADDRESS] de la ruta suele ser una dirección de correo electrónico con el sufijo @<Cloud-Project-ID>.appspotmail.com. Los correos electrónicos que se envíen a la app en este formato se enrutarán a la función.

Python 3 no requiere que la aplicación especifique una secuencia de comandos del controlador en el archivo app.yaml, por lo que puedes quitar todas las secciones de handler en app.yaml.

Tu archivo app.yaml debe mantener las siguientes líneas:

inbound_services:
- mail
- mail_bounce

Envía correos electrónicos

No es necesario que realices cambios en la configuración de tu app cuando actualizas a Python 3. El comportamiento, las funciones y las instrucciones de configuración para enviar correos electrónicos siguen siendo los mismos. Consulta las siguientes guías para obtener más detalles:

Cómo recibir correos electrónicos

Para recibir correos electrónicos, debes importar el módulo google.appengine.api.mail y usar la clase InboundEmailMessage a fin de representar un correo electrónico. Se debe crear una instancia de esta clase para recuperar el contenido del correo electrónico de la solicitud HTTP entrante.

Antes en Python 2, las apps podían acceder a la clase InboundEmailMessage anulando el método receive() en el controlador InboundEmailHandler de la aplicación web. Esto no es necesario en Python 3. En su lugar, la aplicación debe crear una instancia de un objeto nuevo.

Marcos de trabajo web

Cuando se usan frameworks web de Python, el constructor InboundEmailMessage toma los bytes del cuerpo de la solicitud HTTP. Existen varias formas de crear el objeto InboundEmailMessage en Python 3. Los siguientes son ejemplos de apps de Flask y Django:

Python 3 (Flask)

En Flask, request.get_data() otorga los bytes de solicitud.

@app.route("/_ah/bounce", methods=["POST"])
def receive_bounce():
    bounce_message = mail.BounceNotification(dict(request.form.lists()))

    # Do something with the message
    print("Bounce original: ", bounce_message.original)
    print("Bounce notification: ", bounce_message.notification)

    return "OK", 200

Python 3 (Django)

En Django, request.body proporciona los bytes del cuerpo de la solicitud HTTP.

def receive_mail(request):
    message = mail.InboundEmailMessage(request.body)

    print(f"Received greeting for {message.to} at {message.date} from {message.sender}")
    for _, payload in message.bodies("text/plain"):
        print(f"Text/plain body: {payload.decode()}")
        break

    return HttpResponse("OK")

Para ver las muestras de código completas de esta guía, consulta GitHub.

Otros frameworks que cumplen con WSGI

Para otros frameworks que cumplen con WSGI, recomendamos usar el mismo método que los ejemplos de Flask y Django a fin de crear el InboundEmailMessage. Este método funciona cuando los bytes del cuerpo de la solicitud HTTP están disponibles directamente.

Aplicación WSGI sin framework web

Si tu app es una aplicación WSGI que no usa un framework web, es posible que los bytes del cuerpo de la solicitud HTTP no estén disponibles directamente. Si los bytes del cuerpo de la solicitud HTTP están disponibles directamente, te recomendamos que uses un framework web de Python.

En Python 3, se define un método de fábrica llamado from_environ para el InboundEmailMessage. Este es un método de clase que toma el diccionario environ de WSGI como entrada y se puede usar para cualquier aplicación WSGI.

En el siguiente ejemplo, observa cómo se toma environ como entrada para obtener mail_message:

Python 3 (aplicación WSGI)

def HelloReceiver(environ, start_response):
    if environ["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}")
    for content_type, payload in message.bodies("text/plain"):
        print(f"Text/plain body: {payload.decode()}")
        break

    response = http.HTTPStatus.OK
    start_response(f"{response.value} {response.phrase}", [])
    return ["success".encode("utf-8")]

Recibe notificaciones de rebote

Una notificación de rebote es un mensaje automático enviado por un sistema de correo electrónico que indica un problema con la entrega del mensaje de tu app. Para procesar las notificaciones de rebote, tu aplicación debe hacer coincidir las rutas de URL entrantes con la ruta de acceso /_ah/bounce.

Al igual que InboundEmailMessage, se pudo acceder a la clase BounceNotification para Python 2 mediante la anulación del método receive() en el controlador de aplicación web BounceNotificationHandler.

En Python 3, la aplicación debe crear una instancia del objeto BounceNotification, que se puede crear de varias maneras según el framework web de Python que se use.

Marcos de trabajo web

El objeto BounceNotification se inicializa con los valores que se recuperan mediante una llamada a post_vars.get(key).

Cuando se usa un framework web de Python, como Flask o Django, el constructor de BounceNotification toma un diccionario llamado post_vars, que contiene la solicitud POST de los datos del formulario. Para recuperar los datos, se debe definir el método get() en el objeto de entrada. La key es una lista de valores de entrada que la BounceNotification puede leer y recuperar, y puede ser cualquiera de los siguientes:

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

En la mayoría de los frameworks web, estos datos están disponibles como un diccionario múltiple en el objeto de la solicitud. La mayoría de estos tipos se pueden convertir en un diccionario con strings como clave.

Para todas las claves, excepto raw-message, el valor puede ser cualquier elemento. Por lo general, el valor es un solo valor, como una string, o una lista de valores, como {'to': ['bob@example.com', 'alice@example.com']}. El valor predeterminado para todos los campos es una string vacía. Estos valores propagarán las propiedades original y notification.

Para la clave raw-message, el valor debe ser una entrada válida para el constructor de EmailMessage. Puede ser un solo valor o una lista de un solo valor. La clave raw-message se usa para inicializar la propiedad original_raw_message del objeto.

Python 2 (webapp2)

class LogBounceHandler(BounceNotificationHandler):
    def receive(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)

En Flask, request.form del tipo werkzeug.datastructures.MultiDict proporciona las variables POST. Sin embargo, el método get() para este tipo solo muestra un valor, incluso si hay varios valores para la clave.

Para obtener todos los valores correspondientes a una clave, la aplicación debe llamar a dict(request.form.lists()), lo que da como resultado un diccionario en el que cada valor es una lista.

@app.route("/_ah/bounce", methods=["POST"])
def receive_bounce():
    bounce_message = mail.BounceNotification(dict(request.form.lists()))

    # Do something with the message
    print("Bounce original: ", bounce_message.original)
    print("Bounce notification: ", bounce_message.notification)

    return "OK", 200

Python 3 (Django)

En Django, request.POST del tipo django.http.QueryDict proporciona las variables POST. Sin embargo, el método get() para este tipo solo muestra un valor, incluso si hay varios valores para la clave.

Para obtener todos los valores correspondientes a una clave, la aplicación debe llamar a dict(request.POST.lists()), lo que da como resultado un diccionario en el que cada valor es una lista.

def receive_bounce(request):
    bounce_message = mail.BounceNotification(dict(request.POST.lists()))

    # Do something with the message
    print(f"Bounce original: {bounce_message.original}")
    print(f"Bounce notification: {bounce_message.notification}")

    return HttpResponse("OK")

Aplicación WSGI sin framework web

Si tu aplicación es una aplicación WSGI que no usa un framework web, es posible que las variables de formulario de la solicitud HTTP POST no estén directamente disponibles en un diccionario.

En Python 3, se define un método de fábrica llamado from_environ para la BounceNotification. Este es un método de clase que toma el diccionario environ de WSGI como la entrada y se puede usar para cualquier aplicación WSGI.

En el siguiente ejemplo, observa cómo se toma environ como entrada para obtener bounce_message:

Python 3

def BounceReceiver(environ, start_response):
    if environ["REQUEST_METHOD"] != "POST":
        return ("", http.HTTPStatus.METHOD_NOT_ALLOWED, [("Allow", "POST")])

    bounce_message = mail.BounceNotification.from_environ(environ)

    # Do something with the message
    print("Bounce original: ", bounce_message.original)
    print("Bounce notification: ", bounce_message.notification)

    # Return suitable response
    response = http.HTTPStatus.OK
    start_response(f"{response.value} {response.phrase}", [])
    return ["success".encode("utf-8")]

Muestras de código

Para ver las muestras de código completas de esta guía, consulta GitHub.