Hace poco en un proyecto debíamos incluir un servicio de envío de correo y tomamos la decisión de utilizar  el servicio AWS SES, sin embargo cuando me dirigí a la documentación  me encontré con un código que le daba "muchas vueltas" cuando se quería enviar un correo con un simple adjunto, encontre tambien ejemplos que tenían el mismo problema, adicional muchos de los módulos de esos ejemplos actualmente se consideran legacy así que leyendo un poco me di cuenta que a partir de la versión de python 3.6 se agrego una clase llamada EmailMessage la cual esta muy cool y como veremos a continuación permite escribir un código más corto y más "legible".

Para los requisitos necesitamos tener python 3.6 o superior yo usare  3.7 (no hay que instalar librerias externas) y un servicio ya sea smtp o en cloud para enviar el correo, en mi caso usare AWS SES así que sin mas vayamos al código

from email.message import EmailMessage
from email.headerregistry import Address



def build_email(name_client):
    email_message = EmailMessage()
    email_message["Subject"] = "Registro de Bienvenida"
    email_message["From"] = Address(username="jairo", domain="eceleris.com", display_name="Jairo Andres")
    email_message["To"] = Address(username="destino", domain="gmail.com", display_name="Destino")
    email_message.set_content(f"Bienvenido a nuestra pagina {name_client}")
    send_email(email_message=email_message)
   

def send_email(email_message:EmailMessage):
    client = boto3.client("sesv2")
    response = client.send_email(
        FromEmailAddress="jairo@eceleris.com",
            Destination={
                "ToAddresses": ["destino@gmail.com"],
                
            },
            Content={"Raw": {"Data": email_message.as_string()}},
    )

build_email("Jairo")

Ejecutamos nuestro código

python main.py

y el resultado lo podemos ver en nuestra bandeja de correo

Figura 1 : resultado de ejecutar el código

Vamos a explicar un poco el código , pero nos enfocaremos en el método build_email ya en otro artículo hablaremos de AWS SES

from email.message import EmailMessage
from email.headerregistry import Address



def build_email(name_client):
    email_message = EmailMessage()
    email_message["Subject"] = "Registro de Bienvenida"
    email_message["From"] = Address(username="jairo", domain="eceleris.com", display_name="Jairo Andres")
    email_message["To"] = Address(
        username="jairo", domain="eceleris.com", display_name="Jairo Andres"
    )
    email_message.set_content(f"Bienvenido a nuestra pagina {name_client}")
    send_email(email_message=email_message)
   

vamos a importar dos clases una que se llama EmailMessage y otra Address, para enviar un correo la estructura debe cumplir unos estándares de payloads y headers referenciados en RFC 5322 o RFC 6532 los cuales sería bastante complicados gestionarlos manualmente, estas clases utilizando orientación a objetos y diccionarios nos facilitan la gestión, la primera clase EmailMessage es la más importante  en ella nos permite agregar algunos keys como vemos en el código agregamos 3:

  • "Subject" como su nombre indica será el asunto del mensaje
  • "From" será quien envía el mensaje, aquí se usa la clase Address que es una clase de apoyo para darle cumplimiento  al formato requerido en RFC pero haciéndolo sencillo al programador convirtiendo esto en parámetros
  • "To" Será el destinatario o los destinatarios, aquí igualmente se vuelve utilizar  la clase de apoyo Address  por la misma razón mencionada en el punto anterior (si son varios destinatarios simplemente se usa una lista de Address)

Luego el método set_content() nos permite agregar un texto plano en donde utilizamos f string para  reemplazar el name_client, aquí se resalta  lo importante de la clase EmailMessage por que el automaticamente por debajo agrego unos tipos de información para cumplir el RFC, en la documentación que cité legacy esto se hacía manual, en otro ejemplo abajo veremos la importancia de esto

Finalmente se llama la función send_email la cual se encarga de enviar el correo con AWS SES, el código se redujo sustancialmente a lo que antes se había podido evidenciar en el ejemplo citado de la documentación de  AWS, pero veamos ahora un ejemplo más elaborado en donde tenemos un archivo de cuerpo html y se adjunta una imagen.

from email.message import EmailMessage
from email.headerregistry import Address
import boto3



def open_html(name_client):
    html_data: str = ""
    with open("template.html", mode="r", encoding="utf-8") as data:
        html_data = data.read().replace("\n", "")
        html_data = html_data.replace("{{name_client}}", name_client)

    return html_data


def open_file_image():
    file_image = None
    with open("Wallpaper.png", mode="rb") as file:
        file_image = file.read()

    return file_image


def build_email(name_client):
    email_message = EmailMessage()
    email_message["Subject"] = "Registro de Bienvenida"
    email_message["From"] = Address(
        username="jairo", domain="eceleris.com", display_name="Jairo Andres"
    )
    email_message["To"] = Address(
        username="destino", domain="destino.com", display_name="Destino"
    )
    html_data: str = open_html(name_client)
    email_message.add_alternative(html_data, subtype="html")
    email_message.add_attachment(
        open_file_image(), maintype="image", subtype="png", filename="Wallpaper.png"
    )
    send_email(email_message=email_message)


def send_email(email_message: EmailMessage):
    client = boto3.client("sesv2")
    response = client.send_email(
        FromEmailAddress="jairo@eceleris.com",
        Destination={
            "ToAddresses": ["destino@gmail.com"],
        },
        Content={"Raw": {"Data": email_message.as_string()}},
    )


build_email("Jairo")

ejecutamos nuestro código y el resultado es el siguiente

Figura 2 : resultado de ejecutar el código de envío de correo con adjunto

Listo en este nuevo código hemos agregado dos nuevas funciones y dos archivos, un archivo de tipo html y otro png que pondre a continuacion

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hola mundo</title>
</head>
<body>
    <h3>Bienvenido {{name_client}}</h3>
    <a href="https://jairoandres.com" title="Visita mi pagina">Ir a la Home</a>
</body>
</html>
template.html
Figura 3: Archivo Wallpaper.png adjuntado en el envío de correo

Siguiendo con el código explicaremos las dos nuevas funciones que se han agregado (open_html y open_file_image)

def open_html(name_client):
    html_data: str = ""
    with open("template.html", mode="r", encoding="utf-8") as data:
        html_data = data.read().replace("\n", "")
        html_data = html_data.replace("{{name_client}}", name_client)

    return html_data

La función de open_html consiste en cargar el template.html leerlo en memoria reemplazar algunos \n por espacios vacíos y reemplazar la variable name_client en {{name_client}} después retornar esta información procesada


def open_file_image():
    file_image = None
    with open("Wallpaper.png", mode="rb") as file:
        file_image = file.read()

    return file_image

La función open_file_image básicamente carga la imagen png, la lee y retorna los bytes en memoria, seguido de este se puede  observar que al código en la función build_email  en el objeto email_message se llaman dos métodos nuevos add_alternative y add_attachment

def build_email(name_client):
    email_message = EmailMessage()
    email_message["Subject"] = "Registro de Bienvenida"
    email_message["From"] = Address(
        username="jairo", domain="eceleris.com", display_name="Jairo Andres"
    )
    email_message["To"] = Address(
        username="destino", domain="destino.com", display_name="Destino"
    )
    html_data: str = open_html(name_client)
    email_message.add_alternative(html_data, subtype="html")
    email_message.add_attachment(
        open_file_image(), maintype="image", subtype="png", filename="Wallpaper.png"
    )
    send_email(email_message=email_message)
  • add_alternative permite agregarle al cuerpo del mensaje la información, en esta caso internamente la clase llama a set_content que se llamaba inicialmente, este recibe dos parámetros el html en string y  el subtype que en este caso es html pero podría ser texto plano también
  • add_attachment este método permite agregar adjuntos al correo, se pueden agregar tantos como se quieran y recibe cuatro parámetros el archivo en bytes, el maintype y subtype que son dos divisiones de lo que se conoce como MIME TYPES  (en este caso se dividen del "/") y el nombre del archivo que queremos que se muestre en el adjunto cuando se descargue

Finalmente enviamos el correo con el método send_email, para finalizar el articulo mas que una conclusión es una invitación a usar este módulo no solo por que el módulo mime es legacy si no también por la facilidad con la que nos permite construir la estructura del correo a enviar, en mi experiencia con buenas optimizaciones el código se puede reducir y hacer muchisimo mas legible y expandible para agregar más funcionalidades, aquí pueden encontrar toda la documentación