Para validar um endereço de e-mail no Django você roda três checagens, não uma — e o framework só entrega a primeira. Os validadores do Django confirmam que um endereço tem o formato correto; nunca perguntam se o domínio dele consegue receber mensagens ou se a caixa postal existe. A validação de verdade é feita em camadas: sintaxe, depois uma consulta de DNS, depois uma sondagem da caixa postal via SMTP. Este guia constrói cada camada como um validador do Django que você pode encaixar em um formulário ou em um serializer do Django REST Framework, e mostra onde uma API de verificação assume o controle. Ele continua de onde o guia de Python parou, então o raciocínio de DNS e SMTP de lá segue valendo na íntegra.
A resposta curta#
Use validate_email (ou qualquer EmailField) para a sintaxe, dnspython para a consulta
MX e uma API de verificação para a checagem da caixa postal via SMTP — ligados como três
validadores, o mais barato primeiro, curto-circuitando assim que um deles for decisivo. Não
abra conexões SMTP a partir do seu processo Django para sondar caixas postais: a porta 25 de
saída é bloqueada na maioria dos hosts, e a resposta depende da reputação do IP remetente e de
greylisting que você não vai querer reimplementar. Cada camada descarta endereços de forma
mais barata do que a anterior; só a API consegue aprovar um. Se o pipeline é novidade para
você, o que é verificação de e-mail cobre os
termos primeiro.
Camada 1: validação de formato com os validadores do Django#
O Django já cuida da camada um. O django.core.validators.validate_email é uma
instância de EmailValidator que lança ValidationError em um endereço malformado e
retorna None em um endereço bom:
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
Você raramente o chama à mão. Todo forms.EmailField, models.EmailField e
serializers.EmailField do DRF anexa o EmailValidator automaticamente, então declarar
um campo já lhe dá a validação de sintaxe de graça:
from django import forms
class SignupForm(forms.Form):
email = forms.EmailField() # EmailValidator runs during clean()
Saiba exatamente o que isso lhe garante. O EmailValidator verifica o formato contra uma
gramática derivada da RFC — ele nunca resolve DNS, nunca abre um socket e não faz ideia
se example.com existe. definitely-fake@gmail.com passa.
info@company-that-folded.com passa. typo@gmial.com passa. Todos os três são
não entregáveis, e nenhum validador que só lê a string jamais vai lhe dizer isso,
pela mesma razão pela qual a validação de e-mail por regex falha:
sintaxe e entregabilidade são perguntas diferentes. Uma é um fato sobre a string;
a outra é um fato sobre a internet.
Camada 2: o domínio aceita e-mails?#
Esta é a primeira camada que o Django não lhe entrega, e é barata de acoplar. Um
domínio sem registros MX não consegue receber e-mails de ninguém, então uma consulta de DNS
elimina domínios mortos, nomes de empresas escritos errado e TLDs inventados. O Django não tem
nenhum resolvedor de MX embutido, então recorra ao dnspython e embrulhe-o em uma função simples:
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
Um validador do Django é apenas um callable que lança ValidationError diante de uma entrada ruim,
então promova a checagem a um validador e ele se encaixa direto na lista validators de qualquer
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
Se você quiser respeitar domínios que aceitam e-mails em um registro A sem MX, recorra
à resolução de A/AAAA quando o conjunto de MX estiver vazio — mas uma checagem de MX cobre a
imensa maioria dos endereços reais.
Camada 3: a caixa postal realmente existe?#
As camadas um e dois só conseguem descartar um endereço. Um domínio pode publicar registros MX
perfeitos e ainda assim não ter caixa postal no endereço que você tem em mãos —
noreply-9f2x@gmail.com tem sintaxe válida em uma rota de e-mail ativa, e ainda é uma
caixa postal que nunca foi criada. Confirmar uma caixa postal específica significa a conversa
de entrega SMTP: conectar ao host de e-mail, emitir RCPT TO, ler a resposta
e desconectar antes de enviar qualquer coisa.
Em princípio, você poderia programar isso a partir do Django com o smtplib. Na prática você
não deveria rodá-lo a partir do seu servidor de aplicação: a maioria dos hosts em nuvem bloqueia a porta 25
de saída, a resposta depende da reputação do IP a partir do qual você conecta, e os
servidores receptores aplicam greylisting e limitam a taxa de remetentes desconhecidos — então uma sondagem que
funciona em um teste local falha silenciosamente, ou faz você entrar em blocklist, em produção.
Como a verificação de e-mail funciona percorre o
pipeline completo, domínios catch-all e tudo mais. Esta é a camada que vale a pena delegar.
O endpoint de verificação da Qualisend roda o pipeline inteiro — sintaxe, DNS e a sondagem da
caixa postal via SMTP — a partir de uma infraestrutura com reputação gerenciada e retorna um veredito. Chame-o
a partir de um validador com requests, lendo sua chave do ambiente:
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"}
A resposta é um envelope { "result": { ... } }. Leia result["status"] —
deliverable, risky, undeliverable ou unknown — junto com um score, uma
reason e sub_flags para características como endereços de papel (role) ou descartáveis. Consulte a
referência da API para conhecer o formato exato dos campos; aqui você só precisa do
status para lançar a exceção diante de um endereço que não vai entregar:
def validate_mailbox(email: str) -> None:
if verify(email)["status"] == "undeliverable":
raise ValidationError("We couldn't confirm a mailbox at this address.")
Você também pode lançar a exceção em risky, ou armazenar o score e deixar passar — isso é
uma decisão de política. Rejeitar undeliverable é o padrão seguro; o
guia de cadastro serverless cobre o quão
rígido ser no ponto da coleta.
Juntando as camadas para validar um endereço de e-mail no Django#
O lugar idiomático para compor as três camadas em um formulário é um método clean_<field>.
O Django chama o clean_email só depois que o próprio EmailValidator do campo
passou, então a sintaxe já está tratada; você adiciona as checagens de domínio e caixa postal em
ordem, cada uma lançando cedo:
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
Essa ordem é todo o truque: o EmailValidator do campo curto-circuita o lixo
antes do seu código rodar, a checagem de DNS local descarta domínios mortos por nada, e
a API só é acionada para endereços que passaram por ambas. Validação na ordem errada — ou
pular as camadas baratas — gasta um crédito a cada erro de digitação.
Uma coisa para decidir de antemão: o que acontece quando a própria chamada de API falha. Um
timeout de rede ou uma exceção do requests dentro do clean_email não deveria entregar a um
cliente de verdade um erro de validação que ele não consegue corrigir. Capture a falha da requisição
separadamente do ValidationError, e trate um serviço inacessível como unknown
em vez de undeliverable — deixe o cadastro passar e reverifique o endereço
depois, em vez de bloquear o registro por causa de uma indisponibilidade transitória. As camadas de sintaxe e MX
já rodaram localmente, então você só está suavizando a camada que depende da
rede.
Como ambos os validadores personalizados lançam o ValidationError do Django, exatamente as mesmas
duas funções se encaixam sem alteração em um serializer do Django REST Framework. O hook
validate_<field> do DRF captura essa exceção e a transforma em um 400 limpo:
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
Este é o mesmo formato de três camadas que você vai encontrar nas versões em Node.js e Python deste guia — é a divisão em camadas, não o framework, que faz a validação funcionar. Quando você estiver escolhendo qual serviço de verificação vai sustentar a camada três, a comparação de APIs alinha as opções.
Perguntas frequentes#
O EmailValidator do Django é suficiente para validar um endereço de e-mail?#
Para a sintaxe, sim — o validate_email (e todo EmailField que o usa) é a
checagem certa da primeira camada e melhor do que um regex feito à mão. Mas ele valida o
formato, não a entregabilidade: nunca resolve DNS nem contata um servidor de e-mail, então uma
aprovação significa "parece um e-mail", não "vai entregar". Combine-o com uma consulta MX e
uma verificação de caixa postal via SMTP antes de confiar no endereço.
Como escrevo um validador de e-mail personalizado no Django?#
Um validador é qualquer callable que recebe o valor e lança
django.core.exceptions.ValidationError em caso de falha. Defina uma função como
validate_deliverable_domain(email) que lança a exceção quando há um problema e retorna
None caso contrário, depois passe-a na lista validators=[...] do campo ou chame-a
a partir do método clean_email de um formulário. O mesmo callable funciona no hook
validate_email de um serializer do DRF, porque o DRF também captura o ValidationError do Django.
Consigo verificar se uma caixa postal existe a partir do Django sem uma API?#
Em parte. O dnspython confirma que o domínio aceita e-mails, o que elimina domínios mortos
de graça e não exige nada além de uma pequena dependência. Confirmar a caixa postal
significa uma conversa SMTP que você pode tentar com o smtplib, mas não deveria rodar a partir do
seu servidor de aplicação — a porta 25 é amplamente bloqueada e o resultado depende da reputação
do seu IP. Essa é a camada que um serviço de verificação existe para resolver.
Devo rodar essas checagens no cadastro ou ao limpar uma lista?#
Ambos, em profundidades diferentes. Rode as camadas de sintaxe e MX de forma síncrona no
clean_email — elas são rápidas o bastante para bloquear a requisição e dar feedback instantâneo
— e aja também sobre o veredito da API ali. Reserve a verificação em lote, mais pesada, para a
limpeza de listas e o trabalho de bastidor, onde a latência não importa e você pode
processar endereços em massa.
Pronto para adicionar a camada SMTP? Solte um endereço sintaticamente perfeito no
verificador de e-mail gratuito para ver uma string aprovada por um EmailField
voltar como undeliverable, e depois ligue o mesmo veredito aos seus formulários com a
referência da API — com exemplos de copiar e colar incluídos.