SMTP para Python — correo de su script o app Django
Python trae SMTP integrado: con smtplib envía un mensaje en unas pocas líneas, y Django lo agrupa en su propia capa de correo con los ajustes EMAIL. Lo que no aporta ninguno de los dos es la entregabilidad: eso depende del servidor por el que envía.
Tanto si tiene un script suelto como una aplicación Flask o un proyecto Django, los ajustes son los mismos cinco datos. Los apunta a su relay y el código que compone los mensajes se queda igual.
Por qué falla sin relay
El correo de Python sale a menudo desde el segundo plano: un informe nocturno, una tarea programada que envía facturas, un script que avisa cuando una medición se sale del margen. Nadie está mirando, así que nadie nota que el servidor receptor los rechaza en silencio. En Django se suma la sorpresa clásica al publicar: en desarrollo todo va a la consola y en producción a proveedores reales que sí comprueban la reputación.
Configurar el SMTP en Python
Obtener los datos
Acceda a su cuenta de 2mail y obtenga allí su servidor SMTP, su nombre de usuario y su contraseña.
Introducirlos en Python
En un proyecto Django pone los ajustes EMAIL en la configuración: el backend SMTP como backend de correo, el host del relay, el puerto 587, la opción STARTTLS activada, el usuario y la contraseña, y una dirección de remitente por defecto en su propio dominio. Si trabaja directamente con smtplib, conecta con el host en el puerto 587, llama a starttls, inicia sesión con usuario y contraseña y envía después el mensaje. En ambos casos, lea los datos de variables de entorno y no del código.
Autenticar el dominio
Asegúrese de que SPF, DKIM y DMARC de su dominio son correctos, para que los destinatarios puedan verificar que el correo viene de usted.
Probar
Envíe desde su plataforma un mensaje de prueba a una dirección de un gran proveedor y compruebe que llega a la bandeja de entrada y no a la carpeta de spam.
Los campos que pide Python
Cada plataforma los llama de una manera algo distinta, pero siempre son los mismos cinco. Los valores concretos dependen de su cuenta y los encuentra en su cuenta de 2mail: cópielos de ahí para asegurarse de usar los correctos.
| Campo | Qué introducir |
|---|---|
| Servidor SMTP (host) | El nombre del servidor de su cuenta de 2mail. |
| Puerto | El puerto que corresponde al cifrado elegido; su cuenta le indica cuál. |
| Cifrado | TLS. No envíe nunca sin cifrar: de lo contrario sus credenciales viajan en claro. |
| Nombre de usuario | El acceso SMTP de su cuenta de 2mail. |
| Contraseña | La contraseña correspondiente. No use nunca la contraseña del buzón de un empleado. |
Configuración SMTP para Python en código
Ejemplos que funcionan para el puerto 587 con STARTTLS, listos para copiar. Sustituya la dirección de remitente por una dirección de su propio dominio y compruebe el nombre del servidor y el acceso en su cuenta de 2mail. La contraseña va en una variable de entorno: nunca en el código y nunca en el control de versiones.
smtplib: conectar por el 587 con starttls()
Basta con la biblioteca estándar. El orden importa: primero starttls() y solo después login(); de lo contrario su contraseña viaja sin cifrar, o el relay rechaza el inicio de sesión. Use smtplib.SMTP y no SMTP_SSL: este último corresponde al puerto 465. El acceso y la contraseña salen de variables de entorno.
import os
import smtplib
import ssl
from email.message import EmailMessage
msg = EmailMessage()
msg["From"] = "Example <noreply@example.com>"
msg["To"] = "you@example.org"
msg["Subject"] = "SMTP test"
msg.set_content("Sent through the 2mail SMTP relay.")
context = ssl.create_default_context()
with smtplib.SMTP("relay.2mail.eu", 587, timeout=10) as server:
server.starttls(context=context) # upgrade to TLS first, log in afterwards
server.login(os.environ["SMTP_USERNAME"], os.environ["SMTP_PASSWORD"])
server.send_message(msg) # call again for the next message, same connection Django: los ajustes EMAIL_*
Django no necesita ningún paquete adicional. Fije el backend SMTP de forma explícita: en desarrollo suele estar en el backend de consola, y entonces producción no envía nada mientras todo parece funcionar. sendtestemail es un comando de gestión integrado con el que comprueba los ajustes sin tener que llamar a una vista ni a una tarea.
# settings.py
import os
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "relay.2mail.eu"
EMAIL_PORT = 587
EMAIL_USE_TLS = True # STARTTLS - never together with EMAIL_USE_SSL
EMAIL_HOST_USER = os.environ["SMTP_USERNAME"]
EMAIL_HOST_PASSWORD = os.environ["SMTP_PASSWORD"]
EMAIL_TIMEOUT = 10
DEFAULT_FROM_EMAIL = "Example <noreply@example.com>"
SERVER_EMAIL = "noreply@example.com" # sender of error reports to ADMINS
# Test from the shell:
# python manage.py sendtestemail you@example.org Flask-Mail: los mismos cinco datos
Si trabaja con Flask, Flask-Mail pide los mismos datos con otros nombres. Aquí vale lo mismo: MAIL_USE_TLS para el puerto 587, y MAIL_USE_SSL se queda desactivado.
import os
from flask import Flask
from flask_mail import Mail
app = Flask(__name__)
app.config.update(
MAIL_SERVER="relay.2mail.eu",
MAIL_PORT=587,
MAIL_USE_TLS=True, # STARTTLS
MAIL_USE_SSL=False,
MAIL_USERNAME=os.environ["SMTP_USERNAME"],
MAIL_PASSWORD=os.environ["SMTP_PASSWORD"],
MAIL_DEFAULT_SENDER="noreply@example.com",
)
mail = Mail(app) Qué correos envía Python
Una vez configurado el relay, todos estos pasan por 2mail: no tiene que configurarlos uno a uno.
Errores frecuentes con Python
La opción TLS y el puerto deben coincidir
Django tiene dos ajustes de TLS distintos: uno para STARTTLS y otro para TLS desde el principio. Active solo el ajuste de STARTTLS, que es el que corresponde al puerto 587. Activar los dos da un error de configuración; no activar ninguno envía su acceso sin cifrar, y un relay rechaza eso con razón.
Una conexión por serie, no por mensaje
Un script que abre una conexión SMTP nueva para cada uno de mil mensajes se comporta como un atacante. Abra una conexión, envíe la serie y ciérrela. Django tiene una función para enviar varios mensajes por una misma conexión.
Errores silenciosos en tareas de fondo
Una excepción en una tarea cron desaparece en un archivo de registro que nadie lee. Capture los errores al enviar y notifíquelos en algún sitio donde se vean, y conecte los webhooks de 2mail para la entrega y el rebote, para ver también lo que falla tras la entrega.
Mensajes de error habituales con Python
Los mensajes que recibe cuando el host, el puerto, el cifrado o el acceso no son correctos: citados literalmente, para que los reconozca en su registro, con la causa y la solución al lado.
| Mensaje | Causa | Solución |
|---|---|---|
smtplib.SMTPAuthenticationError: (535, b'5.7.8 Error: authentication failed: …') | El relay rechaza el acceso: un usuario o una contraseña incorrectos, o una variable de entorno que en el contexto de cron o del servicio tiene un valor distinto al de su shell. | Imprima lo que el proceso lee realmente (el usuario, nunca la contraseña) y use el acceso SMTP de su cuenta de 2mail, no la contraseña de un buzón. |
smtplib.SMTPNotSupportedError: SMTP AUTH extension not supported by server. | login() se llama antes que starttls(). Un relay solo ofrece la autenticación una vez cifrada la conexión; en la conexión aún sin cifrar, AUTH sencillamente no existe. | Llame primero a server.starttls(context=context) y solo después a server.login(…). En Django: ponga EMAIL_USE_TLS = True. |
ssl.SSLError: [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:…) | El código usa smtplib.SMTP_SSL (o, en Django, EMAIL_USE_SSL) en el puerto 587. Python empieza entonces directamente con una negociación TLS, mientras que el relay espera primero SMTP sin cifrar en ese puerto y solo cambia tras STARTTLS. La clásica confusión entre el 465 y el 587. | Use smtplib.SMTP(…, 587) seguido de starttls(), o en Django EMAIL_USE_TLS = True con el puerto 587. |
smtplib.SMTPServerDisconnected: Connection unexpectedly closed | La conexión se ha cortado antes de la conversación o durante ella. Lo habitual es un script que mantiene una conexión abierta y la reutiliza tras una pausa larga, cuando el servidor ya la ha cerrado por inactividad; o bien una conexión SMTP sin cifrar a un puerto que espera TLS desde el primer byte. | Abra una conexión por serie de mensajes y ciérrela después; capture la excepción y vuelva a conectar. Compruebe que usa el puerto 587 con starttls(). |
ValueError: EMAIL_USE_TLS/EMAIL_USE_SSL are mutually exclusive, so only set one of those settings to True. | En los ajustes de Django están activadas las dos opciones de TLS, a menudo porque una viene de un archivo base compartido y la otra de los ajustes de producción. | Deje solo EMAIL_USE_TLS = True y ponga EMAIL_USE_SSL = False. |
¿Otra plataforma?
SMTP es un protocolo estándar: cualquier sistema que hable SMTP —un CRM, un ERP, una aplicación propia o un servidor— puede enviar a través de 2mail con esos mismos cinco campos.