API Mail para Python

Esta página descreve como usar a API Mail, um dos serviços agrupados antigos, com o runtime Python para o ambiente padrão. A sua app pode aceder aos serviços incluídos através do SDK dos serviços do App Engine para Python.

Vista geral

Em Python, a funcionalidade de processamento de correio está incluída no módulo google.appengine.api.mail. Isto é diferente do Python 2, em que o módulo mail_handlers era fornecido pela webapp. A API Mail para Python pode ser usada para receber emails e notificações de rejeição.

Usar a API Mail

A sua app recebe correio quando um email é enviado como o corpo do pedido num pedido HTTP POST. Para que a app processe os emails recebidos, tem de corresponder ao URL com o caminho /_ah/mail/[ADDRESS]. Normalmente, a parte [ADDRESS] do caminho é um endereço de email com o sufixo @<Cloud-Project-ID>.appspotmail.com. Os emails enviados para a app neste formato são encaminhados para a função.

O Python não exige que a app especifique um script de controlador no ficheiro app.yaml, pelo que pode remover todas as secções handler em app.yaml.

O ficheiro app.yaml deve manter as seguintes linhas:

inbound_services:
- mail
- mail_bounce

A enviar correio

Não precisa de fazer alterações à configuração da sua app quando atualizar para o Python. O comportamento, as funcionalidades e as instruções de configuração para o envio de correio permanecem os mesmos. Consulte os seguintes guias para ver mais detalhes:

Receber correio

Para receber correio, tem de importar o módulo google.appengine.api.mail e usar a classe InboundEmailMessage para representar um email. Esta classe tem de ser instanciada para obter o conteúdo do email do pedido HTTP de entrada.

Anteriormente, no Python 2, as apps podiam aceder à classe InboundEmailMessage ao substituir o método receive() no controlador webapp InboundEmailHandler. Isto não é necessário em Python. Em alternativa, a app tem de instanciar um novo objeto.

Frameworks Web

Quando usar frameworks Web Python, o construtor InboundEmailMessage recebe os bytes do corpo do pedido HTTP. Existem várias formas de criar o objeto InboundEmailMessage em Python. Seguem-se exemplos para apps Flask e Django:

Python 3 (Flask)

No Flask, request.get_data() dá os bytes do pedido.

@app.route("/_ah/mail/<path>", methods=["POST"])
def receive_mail(path):
    message = mail.InboundEmailMessage(request.get_data())

    # Do something with the message
    print(
        f"Received greeting for {escape(message.to)} at {escape(message.date)} from {escape(message.sender)}"
    )
    for content_type, payload in message.bodies("text/plain"):
        print(f"Text/plain body: {payload.decode()}")
        break

    return "OK", 200

Python 3 (Django)

No Django, request.body dá os bytes do corpo do pedido 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 os exemplos de código completos deste guia, consulte o GitHub.

Outras frameworks compatíveis com WSGI

Para outras frameworks compatíveis com WSGI, recomendamos que use o mesmo método que os exemplos do Flask e Django para criar o InboundEmailMessage. Este método funciona quando os bytes do corpo do pedido HTTP estão diretamente disponíveis.

App WSGI sem uma framework Web

Se a sua app for uma app WSGI que não usa uma framework Web, é possível que os bytes do corpo do pedido HTTP não estejam diretamente disponíveis. Se os bytes do corpo do pedido HTTP estiverem diretamente disponíveis, recomendamos que use uma framework Web Python.

Em Python, é definido um método de fábrica denominado from_environ para InboundEmailMessage. Este método é um método de classe que recebe o dicionário WSGI environ como entrada e pode ser usado para qualquer aplicação WSGI.

No exemplo seguinte, repare como environ é usado como entrada para obter o valor de mail_message:

Python 3 (app 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")]

Receção de notificações de rejeição

Uma notificação de rejeição é uma mensagem automática de um sistema de email que indica um problema com a entrega de mensagens da sua app. Para processar notificações de rejeição, a sua app tem de fazer corresponder os caminhos de URL recebidos ao caminho /_ah/bounce.

Tal como InboundEmailMessage, a classe BounceNotification para o Python 2 era acessível ao substituir o método receive() no controlador da app Web BounceNotificationHandler.

Em Python, a app tem de instanciar o objeto BounceNotification, que pode ser criado de várias formas, consoante a framework Web Python usada.

Frameworks Web

O objeto BounceNotification é inicializado com os valores que são obtidos através da chamada de post_vars.get(key).

Quando usa uma framework Web Python, como Flask ou Django, o construtor BounceNotification recebe um dicionário denominado post_vars, que contém o pedido POST dos dados do formulário. Para obter os dados, o método get() tem de ser definido no objeto de entrada. O key é uma lista de valores de entrada que podem ser lidos e obtidos por BounceNotification e podem ser qualquer um dos seguintes:

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

Na maioria das frameworks Web, estes dados estão disponíveis como um dicionário múltiplo no objeto de pedido. A maioria destes tipos pode ser convertida num dicionário com chaves de strings.

Para todas as chaves, exceto raw-message, o valor pode ser qualquer coisa. Normalmente, o valor é um valor único, como uma string, ou uma lista de valores, como {'to': ['bob@example.com', 'alice@example.com']}. O valor predefinido para todos os campos é uma string vazia. Estes valores vão preencher as propriedades original e notification.

Para a chave raw-message, o valor tem de ser uma entrada válida para o construtor de EmailMessage. Pode ser um valor único ou uma lista de valor único. A chave raw-message é usada para inicializar a propriedade original_raw_message do 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)

Em Flask, request.form do tipo werkzeug.datastructures.MultiDict dá as variáveis POST. No entanto, o método get() para este tipo só devolve um valor, mesmo que existam vários valores para a chave.

Para obter todos os valores correspondentes a uma chave, a app tem de chamar dict(request.form.lists()), o que resulta num dicionário em que cada valor é uma 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)

No Django, request.POST do tipo django.http.QueryDict dá as variáveis POST. No entanto, o método get() para este tipo só devolve um valor, mesmo que existam vários valores para a chave.

Para obter todos os valores correspondentes a uma chave, a app tem de chamar dict(request.POST.lists()), o que resulta num dicionário em que cada valor é uma 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")

App WSGI sem uma framework Web

Se a sua app for uma app WSGI que não usa uma framework Web, é possível que as variáveis do formulário do pedido HTTP POST não estejam diretamente disponíveis num dicionário.

Em Python, é definido um método de fábrica denominado from_environ para BounceNotification. Este método é um método de classe que usa o dicionário WSGI environ como entrada e pode ser usado para qualquer aplicação WSGI.

No exemplo seguinte, repare como environ é usado como entrada para obter o valor de 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")]

Exemplos de código

Para ver os exemplos de código completos deste guia, consulte o GitHub.