Enviar correo electrónico en HTML y con adjuntos vía SMTP

Enviar correo electrónico en HTML y con adjuntos vía SMTP

A partir de Python 3.6, la librería estándar incluye el paquete email para construir mensajes de correo electrónico (que eventualmente pueden contener código HTML y archivos adjuntos) y el módulo smtplib para enviarlos a través del protocolo SMTP, con la posibilidad de emplear conexiones seguras SSL y TLS. Para versiones anteriores a la 3.6, incluyendo Python 2.x, véase nuestro artículo anterior sobre el tema: Enviar correo electrónico vía SMTP. En este artículo veremos cómo construir y enviar un correo electrónico en Python a través de cualquier servidor SMTP (Gmail, Outlook, Yahoo, etc., o un servicio propio).

Empecemos enviando un correo electrónico en texto plano. El primer paso es importar los módulos necesarios. El paquete email tiene varios módulos (email.message, email.parser, etc.) para trabajar con correos electrónicos. A nosotros nos interesa únicamente email.message, que contiene la clase necesaria (EmailMessage) para crear un correo electrónico. Por otro lado, necesitaremos del módulo smtplib para, una vez creado el mensaje, enviarlo a través de un servidor SMTP.

from email.message import EmailMessage
import smtplib

Luego, definamos tres variables que contengan el remitente del mensaje (la casilla de correo electrónico desde donde se envía), el destinatario (la dirección a la cual se envía) y el mensaje en sí mismo (en texto plano o en HTML).

remitente = "remitente@ejemplo.com"
destinatario = "destinatario@ejemplo.com"
mensaje = "¡Hola, mundo!"

Deberás reemplazar el contenido de remitente y destinatario por las direcciones de correo electrónico reales. Si estás usando un servicio como Gmail, Outlook o Yahoo, asegúrate de poner en la variable remitente la dirección de correo electrónico de la cuenta desde la cual quieres enviar el mensaje.

Ahora utilicemos estos datos para construir el mensaje a través de la clase email.message.EmailMessage:

email = EmailMessage()
email["From"] = remitente
email["To"] = destinatario
email["Subject"] = "¡Enviado desde Python!"
email.set_content(mensaje)

Las instancia email es como un diccionario en el cual se definen las claves "From" (desde donde se envía el mensaje), "To" (hacia donde se envía), "Subject" (el asunto) y el contenido mismo, esto es, el cuerpo del mensaje. Por defecto, el método set_content(), que define el cuerpo del correo electrónico, supone que el mensaje pasado como argumento está en texto plano.

Una vez creado el correo electrónico, debemos enviarlo a través de alguno de los múltiples protocolos existentes. En este caso utilizaremos SMTP con el auxilio del módulo estándar smtplib. Lo primero que hay que hacer es realizar la conexión al servidor correspondiente (típicamente un dominio, como smtp.gmail.com o smtp.ejemplo.com, o una dirección de IP).

smtp = smtplib.SMTP("smtp.ejemplo.com")

Consulta la documentación de tu proveedor de correo electrónico para saber la dirección exacta del servidor SMTP. La clase smtplib.SMTP establece por defecto una conexión no segura. El puerto por defecto es 25. Sin embargo, prácticamente todos los servidores SMTP modernos requerirán una conexión segura usando SSL o TLS. Si tu proveedor de correo electrónico requiere TLS, utilícese, en su lugar, este código:

# El puerto del protocolo TLS es generalmente 587.
smtp = smtplib.SMTP("smtp.ejemplo.com", port=587)
# Iniciar la conexión segura vía TLS.
smtp.starttls()

Si el servidor SMTP requiere, en vez de TLS, una conexión segura vía SSL, entonces debemos emplear la clase smtplib.SMTP_SSL (la llamada a starttls() no es necesaria en este caso):

smtp = smtplib.SMTP_SSL("smtp.ejemplo.com")

La clase smtplib.SMTP_SSL usa por defecto el puerto 465, que es el que habitualmente se emplea para conexiones SSL. Muchos servidores de correo electrónico (como Gmail) soportan indistintamente SSL y TLS, por lo cual cualquiera de las dos formas anteriores será válida.

Por último, ya establecida la conexión al servidor SMTP, debemos autenticarnos con nuestras credenciales (usuario y contraseña) y enviar el correo construido.

smtp.login(remitente, "clave123")
smtp.sendmail(remitente, destinatario, email.as_string())
smtp.quit()

Por lo general el usuario que se pasa como argumento al método login() es la misma dirección de correo electrónico desde la cual enviamos el mensaje, definida en la variable remitente. En servicios como Gmail, Outlook o Yahoo, la contraseña pasada como segundo argumento (en el ejemplo, "clave123") es la misma contraseña de tu cuenta. El método sendmail() es el encargado de enviar el correo electrónico al destinatario. Finalmente, cerramos la conexión con el servidor vía el método quit().

Código completo:

from email.message import EmailMessage
import smtplib

remitente = "remitente@ejemplo.com"
destinatario = "destinatario@ejemplo.com"
mensaje = "¡Hola, mundo!"

email = EmailMessage()
email["From"] = remitente
email["To"] = destinatario
email["Subject"] = "¡Enviado desde Python!"
email.set_content(mensaje)

smtp = smtplib.SMTP_SSL("smtp.ejemplo.com")
# O si se usa TLS:
# smtp = SMTP("smtp.ejemplo.com", port=587)
# smtp.starttls()
smtp.login(remitente, "clave123")
smtp.sendmail(remitente, destinatario, email.as_string())
smtp.quit()

Enviar correo electrónico en formato HTML

Para incluir código HTML en un correo electrónico, simplemente hay que indicar el argumento subtype="html" al definir el contenido del mensaje vía el método set_content().

from email.message import EmailMessage
import smtplib

remitente = "remitente@ejemplo.com"
destinatario = "destinatario@ejemplo.com"
mensaje = "¡<strong>Hola</strong>, <em>mundo</em>!"

email = EmailMessage()
email["From"] = remitente
email["To"] = destinatario
email["Subject"] = "¡Enviado desde Python!"
email.set_content(mensaje, subtype="html")

smtp = smtplib.SMTP_SSL("smtp.ejemplo.com")
# O si se usa TLS:
# smtp = SMTP("smtp.ejemplo.com", port=587)
# smtp.starttls()
smtp.login(remitente, "clave123")
smtp.sendmail(remitente, destinatario, email.as_string())
smtp.quit()

Adjuntar archivos

Podemos adjuntar archivos al correo electrónico llamando al método add_attachment() tantas veces como sea necesario. Por ejemplo, si queremos adjuntar el archivo adjunto.zip, luego de establecer el cuerpo del mensaje haremos lo siguiente:

with open("adjunto.zip", "rb") as f:
    email.add_attachment(
        f.read(),
        filename="adjunto.zip",
        maintype="application",
        subtype="zip"
    )

filename="adjunto.zip" indica el nombre que tendrá el archivo en el correo electrónico, que puede diferir del nombre original del archivo tal como se leyó vía la función open(). Los argumentos maintype y subtype indican el tipo del archivo según la especificación MIME. Puedes ver una lista de valores válidos para esos dos argumentos según el tipo de archivo en este enlace. Así, por ejemplo, si el tipo MIME para archivos en formato .zip es application/zip (según la lista del enlace anterior), entonces el maintype es "application" y el subtype es "zip".

Código completo:

from email.message import EmailMessage
import smtplib

remitente = "remitente@ejemplo.com"
destinatario = "destinatario@ejemplo.com"
mensaje = "¡<strong>Hola</strong>, <em>mundo</em>!"

email = EmailMessage()
email["From"] = remitente
email["To"] = destinatario
email["Subject"] = "¡Enviado desde Python!"
email.set_content(mensaje, subtype="html")
with open("adjunto.zip", "rb") as f:
    email.add_attachment(
        f.read(),
        filename="adjunto.zip",
        maintype="application",
        subtype="zip"
    )

smtp = smtplib.SMTP_SSL("smtp.ejemplo.com")
# O si se usa TLS:
# smtp = SMTP("smtp.ejemplo.com", port=587)
# smtp.starttls()
smtp.login(remitente, "clave123")
smtp.sendmail(remitente, destinatario, email.as_string())
smtp.quit()

Curso online 👨‍💻

¡Ya lanzamos el curso oficial de Recursos Python en Udemy! Un curso moderno para aprender Python desde cero con programación orientada a objetos, SQL y tkinter en 2024.

Consultoría 💡

Ofrecemos servicios profesionales de desarrollo y capacitación en Python a personas y empresas. Consultanos por tu proyecto.

Deja una respuesta