Skip to content
Empieza con 100 créditos de verificación gratis
Qualisend
Todos los artículos
Ingeniería / 11 de junio de 2026

Cómo validar una dirección de correo electrónico en Django

8 minutes read

Qualisend team
Ventana de código titulada validators.py que muestra tres capas de validación de Django que van estrechando el filtro —validate_email, una resolución MX y una petición POST a la API de verificación— y que terminan en una insignia verde de "entregable".

Para validar una dirección de correo electrónico en Django ejecutas tres comprobaciones, no una, y el framework solo trae la primera de serie. Los validadores de Django confirman que una dirección tiene la forma correcta; nunca preguntan si su dominio puede recibir correo ni si el buzón existe. La validación de verdad va por capas: sintaxis, después una consulta DNS y luego un sondeo del buzón por SMTP. Esta guía construye cada capa como un validador de Django que puedes incorporar a un formulario o a un serializador de Django REST Framework, y muestra dónde toma el relevo una API de verificación. Retoma el hilo donde lo deja la guía de Python, así que el razonamiento sobre DNS y SMTP de allí sigue siendo válido aquí sin cambios.

La respuesta corta#

Usa validate_email (o cualquier EmailField) para la sintaxis, dnspython para la consulta MX y una API de verificación para la comprobación del buzón por SMTP, conectados como tres validadores, del más barato al más caro y cortocircuitando en cuanto uno sea decisivo. No abras conexiones SMTP desde tu proceso de Django para sondear buzones: el puerto 25 de salida está bloqueado en la mayoría de los hosts, y la respuesta depende de la reputación de la IP emisora y del greylisting que no querrás reimplementar. Cada capa descarta direcciones de forma más barata que la anterior; solo la API puede dar por buena una. Si esta canalización es nueva para ti, qué es la verificación de correo explica primero los términos.

Capa 1: validación de formato con los validadores de Django#

Django ya se encarga de la primera capa. django.core.validators.validate_email es una instancia de EmailValidator que lanza ValidationError ante una dirección mal formada y devuelve None ante una correcta:

from django.core.validators import validate_email
from django.core.exceptions import ValidationError

def is_valid_syntax(email: str) -> bool:
    try:
        validate_email(email)
        return True
    except ValidationError:
        return False

Rara vez lo llamas a mano. Cada forms.EmailField, models.EmailField y serializers.EmailField de DRF adjunta EmailValidator automáticamente, de modo que declarar un campo te da la validación de sintaxis gratis:

from django import forms

class SignupForm(forms.Form):
    email = forms.EmailField()  # EmailValidator runs during clean()

Ten claro qué te ofrece esto exactamente. EmailValidator comprueba la forma contra una gramática derivada de los RFC: nunca resuelve el DNS, nunca abre un socket y no tiene ni idea de si example.com existe. definitely-fake@gmail.com pasa. info@company-that-folded.com pasa. typo@gmial.com pasa. Las tres son inentregables, y ningún validador que solo lea la cadena te lo dirá jamás, por la misma razón por la que la validación de correo con regex falla: sintaxis y entregabilidad son preguntas distintas. Una es un hecho sobre la cadena; la otra es un hecho sobre internet.

Capa 2: ¿acepta correo el dominio?#

Esta es la primera capa que Django no te da hecha, y es barata de añadir. Un dominio sin registros MX no puede aceptar correo para nadie, así que una sola consulta DNS elimina los dominios muertos, los nombres de empresa mal escritos y los TLD inventados. Django no incluye un resolver de MX, así que recurre a dnspython y envuélvelo en una función corriente:

import dns.resolver  # pip install dnspython

def has_mail_route(domain: str) -> bool:
    try:
        return len(dns.resolver.resolve(domain, "MX")) > 0
    except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.NoNameservers):
        return False

Un validador de Django no es más que un invocable que lanza ValidationError ante una entrada incorrecta, así que asciende la comprobación a validador y encaja directamente en la lista validators de cualquier campo:

from django.core.exceptions import ValidationError

def validate_deliverable_domain(email: str) -> None:
    domain = email.rsplit("@", 1)[-1]
    if not has_mail_route(domain):
        raise ValidationError("This domain can't receive email.")
validate_deliverable_domain("jane@gmail.com")               # passes
validate_deliverable_domain("jane@company-that-folded.com") # raises ValidationError

Si quieres respetar los dominios que aceptan correo en un registro A sin MX, recurre a resolver A/AAAA cuando el conjunto de MX esté vacío, pero una comprobación de MX cubre la inmensa mayoría de las direcciones reales.

Capa 3: ¿existe realmente el buzón?#

Las capas uno y dos solo pueden descartar una dirección. Un dominio puede publicar registros MX perfectos y aun así no tener ningún buzón en la dirección que manejas: noreply-9f2x@gmail.com tiene una sintaxis válida sobre una ruta de correo activa, y sigue siendo un buzón que nunca se creó. Confirmar un buzón concreto implica la conversación de entrega SMTP: conectarse al host de correo, emitir RCPT TO, leer la respuesta y desconectarse antes de enviar nada.

En principio podrías programar eso desde Django con smtplib. En la práctica no deberías ejecutarlo desde el servidor de tu aplicación: la mayoría de los hosts en la nube bloquean el puerto 25 de salida, la respuesta depende de la reputación de la IP desde la que te conectas, y los servidores receptores aplican greylisting y limitan el ritmo de los remitentes desconocidos, de modo que un sondeo que funciona en una prueba local falla en silencio, o hace que te incluyan en una lista de bloqueo, en producción. Cómo funciona la verificación de correo recorre la canalización completa, con dominios catch-all incluidos. Esta es la capa que merece la pena delegar.

El endpoint de verificación de Qualisend ejecuta toda la canalización —sintaxis, DNS y el sondeo del buzón por SMTP— desde una infraestructura con reputación gestionada y devuelve un veredicto. Llámalo desde un validador con requests, leyendo tu clave del entorno:

import os
import requests  # pip install requests

QUALISEND_VERIFY_URL = "https://api.qualisend.com/v1/verify"

def verify(email: str) -> dict:
    res = requests.post(
        QUALISEND_VERIFY_URL,
        headers={"Authorization": f"Bearer {os.environ['QUALISEND_API_KEY']}"},
        json={"email": email},
        timeout=10,
    )
    res.raise_for_status()
    return res.json()["result"]  # {"status", "score", "reason", "sub_flags"}

La respuesta es un sobre { "result": { ... } }. Lee result["status"]deliverable, risky, undeliverable o unknown— junto a un score, un reason y sub_flags para rasgos como las direcciones de rol o desechables. Consulta la referencia de la API para conocer la forma exacta de los campos; aquí solo necesitas el estado para lanzar un error ante una dirección que no se va a entregar:

def validate_mailbox(email: str) -> None:
    if verify(email)["status"] == "undeliverable":
        raise ValidationError("We couldn't confirm a mailbox at this address.")

También podrías lanzar un error ante risky, o guardar el score y dejarla pasar: eso es una decisión de política. Rechazar undeliverable es la opción segura por defecto; la guía de registro serverless explica cuán estricto conviene ser en el punto de recogida.

Cómo juntar las capas para validar una dirección de correo en Django#

El lugar idiomático para componer las tres capas en un formulario es un método clean_<field>. Django llama a clean_email solo después de que el propio EmailValidator del campo haya pasado, así que la sintaxis ya está resuelta; tú añades las comprobaciones de dominio y de buzón en orden, cada una lanzando el error cuanto antes:

from django import forms

class SignupForm(forms.Form):
    email = forms.EmailField()  # layer 1 for free

    def clean_email(self):
        email = self.cleaned_data["email"]  # syntax already passed
        validate_deliverable_domain(email)  # layer 2 — cheap, local
        validate_mailbox(email)             # layer 3 — the API round-trip
        return email

Ese orden es todo el truco: el EmailValidator del campo cortocircuita la basura antes de que se ejecute tu código, la comprobación DNS local descarta gratis los dominios muertos, y solo se llama a la API para las direcciones que superaron ambas. Validar en el orden equivocado —o saltarse las capas baratas— gasta un crédito en cada errata.

Una cosa que decidir de antemano: qué pasa cuando falla la propia llamada a la API. Un tiempo de espera de red o una excepción de requests dentro de clean_email no debería devolverle a un cliente real un error de validación que no puede corregir. Captura el fallo de la petición por separado del ValidationError, y trata un servicio inalcanzable como unknown en lugar de undeliverable: deja pasar el registro y vuelve a verificar la dirección más tarde, en vez de bloquear el alta por una caída transitoria. Las capas de sintaxis y MX ya se ejecutaron localmente, así que solo estás suavizando la capa que depende de la red.

Como ambos validadores personalizados lanzan el ValidationError de Django, esas mismas dos funciones se incorporan sin cambios a un serializador de Django REST Framework. El hook validate_<field> de DRF captura esa excepción y la convierte en un 400 limpio:

from rest_framework import serializers

class SignupSerializer(serializers.Serializer):
    email = serializers.EmailField()  # layer 1 for free

    def validate_email(self, value):
        validate_deliverable_domain(value)  # layer 2
        validate_mailbox(value)             # layer 3
        return value

Esta es la misma estructura de tres capas que encontrarás en las versiones para Node.js y Python de esta guía: es el sistema de capas, no el framework, lo que hace que la validación funcione. Cuando elijas qué servicio de verificación respalda la tercera capa, la comparativa de APIs pone las opciones una al lado de la otra.

Preguntas frecuentes#

¿Basta el EmailValidator de Django para validar una dirección de correo?#

Para la sintaxis, sí: validate_email (y todo EmailField que lo usa) es el control de primera capa adecuado y mejor que una expresión regular hecha a mano. Pero valida la forma, no la entregabilidad: nunca resuelve el DNS ni contacta con un servidor de correo, así que un aprobado significa "parece un correo", no "se va a entregar". Combínalo con una consulta MX y una comprobación del buzón por SMTP antes de fiarte de la dirección.

¿Cómo escribo un validador de correo personalizado en Django?#

Un validador es cualquier invocable que recibe el valor y lanza django.core.exceptions.ValidationError en caso de fallo. Define una función como validate_deliverable_domain(email) que lance el error cuando haya un problema y devuelva None en caso contrario, y luego pásala en la lista validators=[...] del campo o llámala desde el método clean_email de un formulario. El mismo invocable funciona en el hook validate_email de un serializador de DRF, porque DRF también captura el ValidationError de Django.

¿Puedo comprobar si existe un buzón desde Django sin una API?#

En parte. dnspython confirma que el dominio acepta correo, lo que descarta gratis los dominios muertos y no necesita nada más que una dependencia pequeña. Confirmar el buzón implica una conversación SMTP que puedes intentar con smtplib pero que no deberías ejecutar desde el servidor de tu aplicación: el puerto 25 está bloqueado en muchos sitios y el resultado depende de la reputación de tu IP. Esa es la capa que un servicio de verificación existe para gestionar.

¿Debo ejecutar estas comprobaciones en el registro o al limpiar una lista?#

Ambas cosas, con profundidades distintas. Ejecuta las capas de sintaxis y MX de forma síncrona en clean_email —son lo bastante rápidas para bloquear la petición y dar respuesta al instante— y actúa también ahí sobre el veredicto de la API. Reserva la verificación por lotes, más pesada, para la limpieza de listas y el trabajo de trastienda, donde la latencia da igual y puedes procesar direcciones en bloque.


¿Listo para añadir la capa SMTP? Introduce una dirección sintácticamente perfecta en el comprobador de correo gratuito para ver cómo una cadena aprobada por EmailField vuelve como undeliverable, y luego conecta ese mismo veredicto a tus formularios con la referencia de la API, con ejemplos para copiar y pegar incluidos.

Your reputation, protected.

Clean your first list in minutes. 100 free credits, no card required.

Get started