Skip to content
Comece com 100 créditos de verificação grátis
Qualisend
Todos os artigos
Engenharia / 17 de junho de 2026

Como validar um endereço de e-mail em Python

5 minutes read

Qualisend team
Uma janela de código Python validando um e-mail pelas camadas de sintaxe, DNS e SMTP

Validar um endereço de e-mail em Python significa três verificações separadas, e a que a maioria dos tutoriais mostra — uma regex — é a menos útil de todas. A validação de verdade é feita em camadas: sintaxe, depois uma consulta de DNS e, então, um teste de caixa postal via SMTP. A biblioteca padrão do Python e uma pequena dependência dão conta das duas primeiras; a terceira é um problema de rede que vale a pena delegar. Este guia constrói cada camada com código funcional.

A resposta curta#

Use uma regex permissiva (ou email.utils.parseaddr) para a sintaxe, o dnspython para a consulta de MX e uma API de verificação para a checagem de caixa postal via SMTP — a mais barata primeiro, encerrando o processo assim que uma delas for decisiva. Não recorra ao smtplib para sondar caixas postais a partir da sua aplicação: a porta 25 de saída fica bloqueada na maioria dos provedores, e o resultado depende da reputação do IP de envio e do greylisting que você não quer reimplementar.

Camada 1: sintaxe#

Mantenha-a permissiva — pegue erros de digitação, não reimplemente a RFC 5322 (o que não ajudaria mesmo):

import re

SYNTAX = re.compile(r'^[^\s@"]+(?:\.[^\s@"]+)*@[^\s@.]+(?:\.[^\s@.]+)+$')

def is_valid_syntax(email: str) -> bool:
    return isinstance(email, str) and len(email) <= 320 and SYNTAX.match(email) is not None
is_valid_syntax("jane@example.com")  # True
is_valid_syntax("not-an-email")      # False
is_valid_syntax("a@@b.com")          # False

O email.utils.parseaddr da biblioteca padrão até analisa um endereço, mas é tolerante por design — parseaddr("nonsense") retorna ('', 'nonsense') sem levantar erro — então é um analisador, não um validador. Uma regex curta é mais clara como barreira de sintaxe do tipo sim/não.

Camada 2: o domínio consegue receber e-mails?#

Um domínio sem registros MX não consegue aceitar e-mail de ninguém. O dnspython os resolve em algumas linhas:

import dns.resolver  # pip install dnspython

def has_mail_route(domain: str) -> bool:
    try:
        answers = dns.resolver.resolve(domain, "MX")
        return len(answers) > 0
    except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.NoNameservers):
        return False
has_mail_route("gmail.com")              # True
has_mail_route("company-that-folded.com")  # False

Essa única consulta elimina domínios mortos e TLDs inventados de graça. Se você quiser contemplar domínios que aceitam e-mail em um registro A sem MX, recorra a resolver A/AAAA quando o conjunto de MX estiver vazio — mas a verificação de MX cobre a grande maioria dos endereços reais.

Camada 3: a caixa postal realmente existe?#

As Camadas 1 e 2 só conseguem descartar um endereço. Confirmar uma caixa postal significa a conversa de entrega via SMTP — e, embora o smtplib consiga abrir uma, rodá-lo a partir da sua aplicação é uma má ideia: a porta 25 é amplamente bloqueada, a resposta depende da reputação do IP de onde você se conecta, e os servidores que recebem aplicam greylisting e limitam a taxa de estranhos. Delegue esta camada.

O POST /verify da Qualisend executa o pipeline completo. As verificações locais retornam de imediato, com o teste SMTP em fila:

import os
import requests  # pip install requests

BASE = "https://app.qualisend.com/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['QUALISEND_API_KEY']}"}

def verify(email: str) -> dict:
    res = requests.post(f"{BASE}/verify", headers=HEADERS, json={"email": email}, timeout=10)
    res.raise_for_status()
    return res.json()

Para validação de cadastro ao vivo, o result imediato costuma ser suficiente para agir. Quando você precisar do veredito confirmado por SMTP, faça polling do job até o teste terminar:

import time

def verify_and_wait(email: str, tries: int = 10, delay: float = 1.5):
    job_id = verify(email)["job_id"]
    for _ in range(tries):
        res = requests.get(f"{BASE}/jobs/{job_id}",
                           params={"include": "results"}, headers=HEADERS, timeout=10)
        job = res.json()
        if job["status"] == "completed":
            return job["results"][0]  # {"status": ..., "score": ..., "reason": ...}
        time.sleep(delay)
    return None  # still processing — treat as unknown, retry later

Verificando uma lista em massa#

Não faça um loop de verify() sobre um CSV, um endereço por vez — isso é uma ida e volta HTTP e um job por linha. Envie a lista inteira para o /verify em uma única requisição, receba de volta um único job_id e faça polling uma vez para o lote todo. O array results volta na ordem de submissão:

def verify_bulk(emails: list[str], tries: int = 60, delay: float = 2.0):
    res = requests.post(f"{BASE}/verify", headers=HEADERS,
                        json={"emails": emails}, timeout=30)
    res.raise_for_status()
    job_id = res.json()["job_id"]

    for _ in range(tries):
        job = requests.get(f"{BASE}/jobs/{job_id}",
                          params={"include": "results"}, headers=HEADERS,
                          timeout=30).json()
        if job["status"] == "completed":
            return dict(zip(emails, job["results"]))
        time.sleep(delay)  # back off; large lists take longer than a single probe
    return None

Um lote de milhares pode levar minutos, porque cada teste SMTP é uma conversa de rede separada, então amplie tries/delay para listas maiores em vez de martelar o endpoint de polling. Rode as duas camadas locais primeiro para descartar o lixo óbvio antes de gastar créditos — filtre com is_valid_syntax e has_mail_route e, depois, envie apenas os sobreviventes.

Notas sobre async e pydantic#

O requests é bloqueante, então, em uma aplicação asyncio, envolva a chamada com asyncio.to_thread(verify, email) ou troque para httpx.AsyncClient para disparar as chamadas de verificação de forma concorrente; o dnspython traz um resolvedor async (dns.asyncresolver) pelo mesmo motivo. Se você já usa pydantic, o tipo EmailStr é um substituto limpo para a Camada 1 — ele valida a sintaxe quando um modelo é construído —, mas para por aí: nunca toca no DNS nem na caixa postal, então mantenha a verificação de MX e a API de verificação a jusante dele.

Juntando as camadas#

A mais barata primeiro, parando assim que você tiver uma resposta:

def validate_email(email: str) -> dict:
    if not is_valid_syntax(email):
        return {"status": "undeliverable", "reason": "invalid_email"}
    domain = email.rsplit("@", 1)[1]
    if not has_mail_route(domain):
        return {"status": "undeliverable", "reason": "invalid_domain"}
    return verify(email)["result"]  # deliverable | risky | undeliverable | unknown

As duas camadas locais não custam nada e pegam a maior parte do lixo na hora; a API roda apenas nos endereços que valem a ida e volta. Veja como funciona a verificação de e-mail para entender por que as etapas ficam nesta ordem.

Perguntas frequentes#

Dá para verificar um e-mail em Python usando só a biblioteca padrão?#

Até certo ponto. O re resolve a sintaxe e, com socket, você pode tentar o DNS — mas não existe um resolvedor de MX embutido (por isso o dnspython), e confirmar uma caixa postal exige um teste SMTP que você não deveria rodar a partir da sua aplicação. A biblioteca padrão dá conta da sintaxe e de uma verificação de domínio superficial; a camada da caixa postal precisa do dnspython mais um serviço de verificação.

Por que não usar o smtplib para checar se uma caixa postal existe?#

O smtplib consegue abrir a conversa SMTP, mas rodá-lo a partir do servidor da sua aplicação é pouco confiável e arriscado: a maioria dos provedores bloqueia a porta 25 de saída, a resposta depende da reputação do seu IP de envio, e sondar em qualquer volume acaba te levando a limitação de taxa ou à blocklist. Uma API de verificação executa o teste a partir de uma infraestrutura com reputação gerenciada, feita para isso.

O dnspython é a biblioteca certa para consultas de MX?#

Sim — é o kit de ferramentas de DNS padrão e bem mantido para Python, e resolve registros MX de forma limpa. Instale com pip install dnspython e capture NXDOMAIN, NoAnswer e NoNameservers para tratar a ausência de rota de e-mail como não entregável.

O EmailStr do pydantic valida um endereço de e-mail por completo?#

Não — o EmailStr (baseado no pacote email-validator) só verifica se uma string é sintaticamente um endereço válido no momento em que o seu modelo é construído. Ele não confirma se o domínio tem registros MX nem se a caixa postal existe, então corresponde exatamente à Camada 1. Use-o como a barreira de sintaxe nos seus modelos pydantic e, depois, rode a consulta de DNS e a API de verificação nos endereços que passarem. Ele também normaliza o endereço (deixa o domínio em minúsculas), o que ajuda antes de você remover duplicatas de uma lista.


Pronto para adicionar a camada SMTP? O plano gratuito inclui 100 créditos que rodam o pipeline completo, e a referência da API traz os endpoints /verify e /jobs completos, com exemplos de copiar e colar em sete linguagens.

Your reputation, protected.

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

Get started