Puedes automatizar el envío de correos electrónicos con Python usando las bibliotecas smtplib y email, que vienen incluidas en la instalación estándar sin necesidad de instalar paquetes externos.
Las dos bibliotecas esenciales
smtplib se encarga de la conexión con el servidor de correo. email construye el mensaje: asunto, cuerpo y archivos adjuntos.
Juntas forman el núcleo de cualquier sistema de notificaciones automáticas. Puedes enviar desde reportes de ventas hasta alertas de error en producción.
Estructura básica de un correo automático
Todo script de envío sigue este esquema:
- Construir el mensaje con
email.mime - Conectarte al servidor SMTP con
smtplib.SMTP - Autenticarte con tu usuario y contraseña
- Enviar y cerrar la conexión
Esta secuencia se repite sin importar si el correo es simple o lleva adjuntos.
Ejemplo 1 — Correo de texto simple
Este script envía una notificación básica. Úsalo para alertas rápidas o confirmaciones de proceso.
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
remitente = "reportes@miempresa.com.mx"
destinatario = "gerente@miempresa.com.mx"
asunto = "Proceso completado"
cuerpo = "El proceso de carga de inventario terminó sin errores."
mensaje = MIMEMultipart()
mensaje["From"] = remitente
mensaje["To"] = destinatario
mensaje["Subject"] = asunto
mensaje.attach(MIMEText(cuerpo, "plain"))
with smtplib.SMTP("smtp.gmail.com", 587) as servidor:
servidor.starttls()
servidor.login(remitente, "tu_contraseña_app")
servidor.sendmail(remitente, destinatario, mensaje.as_string())
print("Correo enviado correctamente.")
Nota importante: En Gmail debes usar una contraseña de aplicación, no tu contraseña normal. La generas en la configuración de seguridad de tu cuenta de Google.
Ejemplo 2 — Reporte con archivo adjunto
Supón que trabajas en FEMSA y cada lunes debes enviar un reporte de ventas en Excel. Este script adjunta el archivo automáticamente.
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
import os
remitente = "automatizacion@femsa.com.mx"
destinatario = "direccion@femsa.com.mx"
asunto = "Reporte semanal de ventas — FEMSA"
cuerpo = "Adjunto encontrarás el reporte de ventas de la semana. Generado automáticamente."
archivo_adjunto = "reporte_ventas_semana.xlsx"
mensaje = MIMEMultipart()
mensaje["From"] = remitente
mensaje["To"] = destinatario
mensaje["Subject"] = asunto
mensaje.attach(MIMEText(cuerpo, "plain"))
with open(archivo_adjunto, "rb") as archivo:
adjunto = MIMEBase("application", "octet-stream")
adjunto.set_payload(archivo.read())
encoders.encode_base64(adjunto)
adjunto.add_header(
"Content-Disposition",
f"attachment; filename={os.path.basename(archivo_adjunto)}"
)
mensaje.attach(adjunto)
with smtplib.SMTP("smtp.gmail.com", 587) as servidor:
servidor.starttls()
servidor.login(remitente, "tu_contraseña_app")
servidor.sendmail(remitente, destinatario, mensaje.as_string())
print("Reporte enviado.")
El objeto MIMEBase con encoders.encode_base64 convierte el archivo binario a un formato seguro para transmisión por correo.
Ejemplo 3 — Notificación con datos variables
Este ejemplo es más cercano a un caso real. Imagina que en Liverpool se generan alertas cuando las ventas del día caen por debajo de una meta.
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
def enviar_alerta_ventas(tienda, ventas_dia, meta):
remitente = "alertas@liverpool.com.mx"
destinatario = "supervisor@liverpool.com.mx"
diferencia = meta - ventas_dia
asunto = f"Alerta: {tienda} por debajo de meta"
cuerpo = (
f"Tienda: {tienda}\n"
f"Ventas del día: ${ventas_dia:,.0f}\n"
f"Meta diaria: ${meta:,.0f}\n"
f"Diferencia: -${diferencia:,.0f}\n"
f"Se requiere revisión inmediata."
)
mensaje = MIMEMultipart()
mensaje["From"] = remitente
mensaje["To"] = destinatario
mensaje["Subject"] = asunto
mensaje.attach(MIMEText(cuerpo, "plain"))
with smtplib.SMTP("smtp.gmail.com", 587) as servidor:
servidor.starttls()
servidor.login(remitente, "tu_contraseña_app")
servidor.sendmail(remitente, destinatario, mensaje.as_string())
print(f"Alerta enviada para {tienda}.")
enviar_alerta_ventas("Perisur", 42000, 60000)
Resultado en consola:
Alerta enviada para Perisur.
Cuerpo del correo generado:
Tienda: Perisur
Ventas del día: $42,000
Meta diaria: $60,000
Diferencia: -$18,000
Se requiere revisión inmediata.
Al envolver la lógica en una función, puedes llamarla dentro de un ciclo para revisar múltiples tiendas en segundos.
Enviar a múltiples destinatarios
Para notificar a todo un equipo, usa una lista y únela con comas.
destinatarios = [
"gerente@bimbo.com.mx",
"logistica@bimbo.com.mx",
"finanzas@bimbo.com.mx"
]
mensaje["To"] = ", ".join(destinatarios)
servidor.sendmail(remitente, destinatarios, mensaje.as_string())
sendmail acepta una lista de correos como segundo argumento. El campo "To" del encabezado es solo visual; la entrega real la controla ese segundo argumento.
Tabla de referencia: componentes MIME
| Clase | Uso |
|---|---|
MIMEMultipart |
Contenedor principal del mensaje |
MIMEText |
Texto plano o HTML en el cuerpo |
MIMEBase |
Archivos adjuntos genéricos |
encoders.encode_base64 |
Codifica archivos binarios para adjuntar |
Siempre usa MIMEMultipart como base. Los demás objetos se adjuntan con .attach().
Tabla de referencia: parámetros SMTP comunes
| Servidor | Host | Puerto | Cifrado |
|---|---|---|---|
| Gmail | smtp.gmail.com | 587 | STARTTLS |
| Outlook / Hotmail | smtp.office365.com | 587 | STARTTLS |
| Yahoo | smtp.mail.yahoo.com | 587 | STARTTLS |
| Servidor propio | tu_dominio.com.mx | 587 / 465 | STARTTLS / SSL |
El puerto 587 con STARTTLS es el estándar moderno. Evita el puerto 25, que muchos proveedores bloquean.
Errores comunes
Error 1 — Usar la contraseña de cuenta en lugar de contraseña de aplicación. Gmail y Outlook bloquean el inicio de sesión si usas tu contraseña normal desde un script. Genera siempre una contraseña de aplicación específica para Python.
Error 2 — No usar starttls() antes de login().
Si omites servidor.starttls(), la conexión no está cifrada y el servidor rechaza la autenticación. Esta línea siempre va antes de login().
Error 3 — Abrir el archivo adjunto en modo texto ("r") en lugar de binario ("rb").
Los archivos Excel, PDF e imágenes deben abrirse con open(archivo, "rb"). Si usas "r" obtendrás un error de codificación.
Error 4 — Hardcodear credenciales en el código fuente.
Nunca escribas tu contraseña directamente en el script si lo vas a compartir o subir a un repositorio. Usa variables de entorno con os.environ.get("CORREO_PASSWORD").
import os
password = os.environ.get("CORREO_PASSWORD")
Esto mantiene tus credenciales fuera del código y protege tu cuenta.
Automatización programada
Enviar correos con Python cobra mayor utilidad cuando el script corre solo, sin intervención manual. En Windows puedes usar el Programador de tareas. En Linux o macOS usas cron.
Ejemplo de entrada en cron para ejecutar el reporte todos los lunes a las 7:00 a.m.:
0 7 * * 1 /usr/bin/python3 /home/usuario/reporte_semanal.py
Combina este script con lo que aprendiste en la lección anterior: genera el Excel con openpyxl o pandas y luego envíalo de forma automática en el mismo flujo.
Puntos clave
- Usa
smtplibpara la conexión yemail.mimepara construir el mensaje completo. - Siempre llama
servidor.starttls()antes deservidor.login()para cifrar la conexión. - Los archivos adjuntos requieren
MIMEBase+encoders.encode_base64y abrirse en modo binario ("rb"). - Nunca pongas contraseñas directamente en el código; usa variables de entorno con
os.environ.get(). - Combina el envío de correos con la generación de archivos Excel para crear reportes automáticos de extremo a extremo.