Cómo verificar direcciones de email en Python
Verificar una dirección de email en Python es sencillo en las partes fáciles y sorprendentemente difícil en la parte que más importa. Esta guía recorre las verificaciones de sintaxis y las consultas DNS que puedes ejecutar en local, explica exactamente por qué las sondas SMTP directas fallan en la práctica y muestra cómo obtener un resultado a nivel de buzón con una API REST.
Usa Python para rechazar entradas mal formadas y, opcionalmente, guardar en caché las verificaciones DNS/MX. Usa una API de verificación para el resultado a nivel de buzón, porque las sondas smtplib directas son fáciles de limitar y a menudo devuelven respuestas ambiguas.
Qué significa realmente "verificar" un email
Verificación de correo es el proceso de confirmar que una dirección es real y puede recibir correo antes de enviarle nada. Se divide en una secuencia de verificaciones, cada una más difícil que la anterior: si la dirección tiene el formato correcto, si su dominio acepta correo y si el buzón concreto existe. En Python, las dos primeras son fáciles de hacer bien. La tercera es donde la mayoría de las soluciones caseras fallan sin hacer ruido, y entender por qué es lo más útil de este artículo.
El plan de abajo va por capas. Ejecuta en local las verificaciones baratas y rápidas para descartar las entradas claramente rotas y luego llama a una API de verificación para obtener la respuesta a nivel de buzón sobre si la dirección se puede usar. Así rechazas la basura al instante sin llamar a la API, y obtienes un resultado más claro para todo lo que pasa el filtro.
Paso 1: validación de sintaxis
La primera verificación confirma que la dirección está bien formada. Es tentador escribir una expresión regular para esto, pero la sintaxis del email es mucho más intrincada de lo que parece, y las expresiones regulares hechas a mano casi siempre son demasiado estrictas o demasiado permisivas. La biblioteca de referencia para esta tarea es email-validator, que implementa correctamente los estándares relevantes y se usa mucho en producción.
Instálala con pip:
pip install email-validator
Después valida una dirección. La biblioteca lanza EmailNotValidError con cualquier entrada mal formada y devuelve un objeto normalizado si la entrada es válida:
from email_validator import validate_email, EmailNotValidError
def check_syntax(address):
try:
result = validate_email(address, check_deliverability=False)
return True, result.normalized
except EmailNotValidError as error:
return False, str(error)
ok, value = check_syntax("jane.doe@example.com")
print(ok, value) # True jane.doe@example.com
ok, value = check_syntax("jane.doe@@example")
print(ok, value) # False, with a description of the problem
Configurar check_deliverability=False mantiene este paso como pura sintaxis, sin llamadas de red, así que es lo bastante rápido para ejecutarlo en cada pulsación de tecla o envío de formulario. Guarda siempre el valor normalized que devuelve la biblioteca en lugar de la entrada original del usuario, ya que canonicaliza la dirección para almacenarla de forma coherente.
Paso 2: verificaciones de dominio y MX
Una dirección con el formato correcto sigue sin servir si su dominio no puede recibir email. La siguiente verificación consulta los registros DNS del dominio y busca registros MX, que indican los servidores de correo responsables de ese dominio. Un dominio sin registros MX no puede aceptar correo, así que cualquier dirección en él puede rechazarse de inmediato.
El email-validator es una biblioteca que puede hacerlo por ti si le pasas check_deliverability=True, que realiza la consulta MX como parte de la validación:
from email_validator import validate_email, EmailNotValidError
def check_domain(address):
try:
result = validate_email(address, check_deliverability=True)
return True, result.domain
except EmailNotValidError as error:
return False, str(error)
Si quieres ejecutar tú mismo la consulta DNS, por ejemplo para guardar en caché los resultados de muchas direcciones del mismo dominio, usa la biblioteca dnspython directamente:
pip install dnspython
import dns.exception
import dns.resolver
def has_mx_record(domain):
try:
records = dns.resolver.resolve(domain, "MX")
return len(records) > 0
except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN):
return False
except dns.exception.DNSException:
return False # Treat resolver errors as inconclusive.
print(has_mx_record("gmail.com")) # True
print(has_mx_record("no-such-domain-xyz.invalid")) # False
Esto es realmente útil. Elimina las direcciones de dominios que han caducado, que nunca existieron o que simplemente no gestionan correo. Pero fíjate en lo que todavía no te ha dicho: si someone@gmail.com es un buzón real. Gmail tiene registros MX, así que todas las direcciones de Gmail pasan esta verificación, sean válidas o no.
Paso 3: la verificación del buzón, y por qué el SMTP directo se queda corto
Confirmar el propio buzón requiere hablar con el servidor de correo receptor. En principio, te conectas por SMTP, te identificas, envías un comando RCPT TO con la dirección objetivo y lees el código de respuesta: un código positivo sugiere que el buzón existe y un rechazo sugiere que no. El módulo integrado de Python smtplib puede hacerlo, y el código parece engañosamente sencillo:
import smtplib
import dns.resolver
def naive_smtp_check(address):
domain = address.split("@")[1]
mx_host = str(dns.resolver.resolve(domain, "MX")[0].exchange)
server = smtplib.SMTP(timeout=10)
server.connect(mx_host)
server.helo("example.com")
server.mail("probe@example.com")
code, message = server.rcpt(address)
server.quit()
# 250 and 251 generally mean the mailbox was accepted.
return code in (250, 251)
Esto funciona en un tutorial y falla en producción. Vale la pena conocer los motivos en detalle, porque explican por qué ningún verificador serio depende de un script como este.
Reputación de IP y limitación
Los proveedores de buzones detectan activamente las sondas de verificación. Lanza unas cuantas verificaciones RCPT TO desde un mismo servidor y proveedores como Gmail y Outlook limitarán o bloquearán esa IP en pocos intentos. Una vez limitado, el servidor devuelve respuestas ambiguas exista o no el buzón, así que tus resultados dejan de tener sentido. La verificación real se hace desde grandes grupos de direcciones IP bien calentadas y bien gestionadas precisamente por este motivo.
Greylisting
Muchos servidores usan greylisting y devuelven a propósito un fallo temporal 4xx la primera vez que un remitente desconocido establece contacto. Los servidores de correo legítimos reintentan más tarde; los spammers normalmente no. Un script ingenuo que no reintenta interpreta ese fallo temporal como un rechazo y marca como inválido un buzón perfectamente bueno. Es un falso negativo causado únicamente por la impaciencia.
Dominios catch-all
Un dominio catch-all acepta correo para cualquier dirección posible, así que RCPT TO devuelve un código positivo tanto para realperson@domain.com y la random-string-9f3a@domain.com sin distinción. Una verificación SMTP directa no puede distinguir un buzón real de uno inexistente en estos dominios. Detectar un catch-all requiere enviar una segunda sonda a una dirección que seguro que no existe y ver si también se acepta, lo que es detección de catch-all, no un simple sí o no.
Respuestas ambiguas de los grandes proveedores
Algunos grandes proveedores han hecho deliberadamente ambiguo su comportamiento con RCPT TO . Algunos aceptan cualquier dirección en la fase del handshake y solo rebotan después, así que la respuesta SMTP simplemente no contiene la información que estás pidiendo.
Por eso los responsables de email-validator dejan fuera deliberadamente las verificaciones SMTP de la biblioteca. Su documentación dice claramente que no se gana nada contactando tú mismo con un servidor SMTP, porque los servidores son buenos ocultando si una dirección es entregable, y una dirección que parece aceptar correo puede rebotar después. La conclusión honesta: las verificaciones de sintaxis y MX son cosas que puedes hacer bien en tu propio código, pero una verificación fiable del buzón no lo es.
Paso 4: verificar con la API de VeriMails
La solución práctica es dejar que un servicio de verificación se encargue de la verificación del buzón desde una infraestructura pensada para ello, y llamar a ese servicio desde Python con una sola petición HTTP. VeriMails ofrece una API REST que ejecuta todo el proceso: sintaxis, MX, DNS, un handshake SMTP en vivo, detección de catch-all, detección de desechables y detección de direcciones de rol, y devuelve un único resultado estructurado.
Con la biblioteca estándar requests , una llamada de verificación tiene este aspecto. Para los detalles del endpoint, consulta la Documentación de la API de VeriMails:
import requests
def verify_email(address, api_key):
response = requests.get(
"https://api.verimails.com/v1/verify",
params={"email": address},
headers={"Authorization": f"Bearer {api_key}"},
timeout=10,
)
response.raise_for_status()
return response.json()
result = verify_email("jane.doe@example.com", api_key="YOUR_API_KEY")
print(result["status"]) # e.g. "deliverable", "undeliverable", "catch_all"
print(result["disposable"]) # True if a throwaway address
print(result["role_based"]) # True for addresses such as info@ or support@
Después puedes actuar según el resultado. Un formulario de registro podría aceptar las direcciones deliverable , rechazar las undeliverable y la disposable y decidir su propia política para las catch_all:
def is_acceptable_for_signup(address, api_key):
result = verify_email(address, api_key)
if result["disposable"]:
return False, "Disposable addresses are not allowed."
if result["status"] == "undeliverable":
return False, "That address does not appear to exist."
return True, "Address verified."
Como cada llamada devuelve un resultado claro, es lo bastante rápida para ejecutarse dentro del envío de un formulario sin hacer esperar al usuario. Para una lista de direcciones que ya está en tu base de datos, la la verificación masiva por CSV encaja mejor, ya que subir un archivo es mucho más eficiente que llamar en bucle al endpoint de una sola dirección miles de veces.
El patrón recomendado para una aplicación en Python es combinar ambas capas: mantén la verificación de sintaxis local con email-validator para rechazar al instante las entradas basura antes de gastar un crédito, y luego llama a la API para obtener el resultado de entregabilidad definitivo de todo lo que pase. Obtienes respuesta rápida ante los errores evidentes y un veredicto fiable para las direcciones reales.
Tabla de políticas de la aplicación
| Resultado | Política de registro | Política de importación o CRM |
|---|---|---|
deliverable | Aceptar y guardar la dirección normalizada | Mantener en el segmento activo |
undeliverable | Rechazar con un mensaje claro de corrección | Suprimir antes de cualquier campaña |
catch_all | Aceptar o revisar según el riesgo de la cuenta | Segmentar por separado y enviar con prudencia |
disposable | Rechazar en la mayoría de los productos | Suprimir de las listas de prospección |
role_based | Permitir solo cuando los buzones compartidos sean aceptables | Revisar antes del cold outreach |
Llamadas a la API frente a trabajos de lista
Usa la API donde importa una decisión inmediata: registro, checkout, formularios de leads, invitaciones y cambios de cuenta. Usa la verificación masiva para una exportación CSV, una limpieza del CRM o una audiencia de campaña en la que miles de direcciones necesitan la misma revisión. Ambos flujos deberían alimentar las mismas reglas de supresión, para que una dirección rechazada por una vía no reciba correo más tarde desde otra herramienta.
Todo junto
Una implementación limpia tiene tres capas. Primero, una verificación de sintaxis con email-validator y check_deliverability=False, ejecutada en local y al instante para rechazar las entradas mal formadas. Segundo, una verificación MX opcional con dnspython si quieres filtrar los dominios muertos antes de llamar a la API, aunque la API también lo cubre. Tercero, una llamada a la API de VeriMails para el resultado a nivel de buzón, incluida la detección de catch-all, desechables y direcciones de rol que no puedes reproducir de forma fiable por tu cuenta.
Lo que hay que recordar es el reparto del trabajo. La sintaxis y el DNS son realmente cosa tuya en Python. La verificación del buzón en vivo depende de la reputación de IP, del comportamiento del greylisting y de la detección de catch-all, que un solo script no puede gestionar, y por eso la biblioteca de validación estándar deja fuera deliberadamente las sondas SMTP. Usa verificaciones locales para ganar velocidad, una API para la verificación a nivel de buzón, y la página de precios para elegir el plan que encaje con el volumen que esperas.
Preguntas frecuentes
Artículos relacionados
Empieza con datos limpios
100 créditos gratis al registrarte. Sin tarjeta de crédito. Pon en práctica los consejos hoy mismo.
Empezar gratis