Validar um endereço de e-mail no Laravel dá vontade de tratar como uma linha só —
adicionar email a um array de validação e seguir em frente. Essa regra é um bom
começo, mas responde apenas à primeira das três perguntas que a validação de
verdade faz. O endereço tem o formato correto? O domínio dele consegue receber
e-mail? A caixa de entrada realmente existe? O Laravel é peculiar porque sua regra
email nativa consegue responder às duas primeiras em uma única linha; a
terceira é um problema de rede que vale a pena delegar. Este guia constrói cada
camada sobre o próprio validador do Laravel e mostra exatamente onde o framework
para. Ele amplia o
guia de validação de e-mail em PHP: o Laravel
envolve os mesmos blocos de construção do PHP — e filter_var está a uma palavra-chave
de distância — em uma API muito mais agradável.
A resposta curta#
Use a regra email:rfc,dns do Laravel para a sintaxe e a consulta MX em uma única
passagem e, depois, uma API de verificação para a checagem SMTP da caixa de
entrada — o mais barato primeiro, encerrando cedo com bail assim que uma camada
for decisiva. Não tente abrir uma sessão SMTP a partir da sua aplicação para
sondar caixas de entrada por conta própria: a porta 25 de saída é bloqueada na
maioria dos hosts, e a resposta depende da reputação do IP de envio e do
greylisting que você não vai querer reimplementar. Cada camada elimina endereços
de forma mais barata que a anterior; só a última consegue aprovar um endereço.
Camadas 1 e 2: sintaxe e DNS em uma só regra#
A regra email é sustentada pelo pacote egulias/email-validator, e ela aceita
estilos que decidem o quão rígida ela é. Por padrão, aplica o estilo rfc
(gramática das RFC 5321/5322). O estilo que importa para a validação é o dns,
que adiciona uma consulta ao vivo e confirma que o domínio resolve uma rota de
e-mail — um registro MX, com um registro A como alternativa. Peça os dois e o
Laravel faz a sintaxe e a checagem de domínio em uma única regra:
public function store(Request $request)
{
$validated = $request->validate([
'email' => ['required', 'email:rfc,dns'],
]);
// $validated['email'] is well-formed AND its domain can receive mail.
}
Os outros estilos vale a pena conhecer. email:filter executa o
filter_var($email, FILTER_VALIDATE_EMAIL) do PHP — exatamente a checagem do
guia de PHP — e filter_unicode permite
algumas partes locais em Unicode. strict rejeita os avisos de ponto final e
pontos consecutivos que o parser da RFC tolera. E spoof protege contra
caracteres homógrafos enganosos, então, para um formulário de cadastro público,
email:rfc,dns,spoof é um padrão sensato. Em um form request fica assim:
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreSubscriberRequest extends FormRequest
{
public function rules(): array
{
return [
'email' => ['required', 'email:rfc,dns,spoof'],
];
}
}
Por que email:rfc,dns ainda não é sinal verde#
Passar em email:rfc,dns significa que a string está bem formada e que o
domínio consegue receber e-mail. Não diz nada sobre a existência daquela caixa de
entrada específica. noreply-9f2x@gmail.com passa. definitely-fake@gmail.com
também. Ambos são inentregáveis, porque o registro MX é um fato sobre o domínio
e a caixa de entrada é um fato que você só descobre perguntando ao servidor de
e-mail. Essa é a mesma parede que faz a
validação de e-mail por regex falhar:
nenhuma dose de correspondência de padrões, e nenhuma consulta DNS, atravessa de
"o domínio aceita e-mail" para "este endereço aceita".
Se quiser ver a diferença você mesmo, cole um endereço sintaticamente perfeito no
verificador de e-mail gratuito e observe uma string
aprovada pela regra voltar como undeliverable.
Camada 3: a caixa de entrada realmente existe?#
Confirmar uma caixa de entrada 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 em PHP, mas
não deveria executá-lo a partir do seu servidor de aplicação — a maioria dos
provedores de nuvem bloqueia a porta 25 de saída, a resposta depende da reputação
do IP de onde você conecta, e os servidores de recebimento fazem greylisting e
limitam a taxa de remetentes desconhecidos. E tem mais:
como funciona a verificação de e-mail
percorre o pipeline completo, incluindo domínios catch-all que aceitam todo
endereço e derrotam uma sondagem ingênua. Esta é a camada que vale a pena delegar.
Chamando a Qualisend a partir de uma regra de validação personalizada#
O Laravel deixa a delegação limpa: envolva a chamada de API em um objeto de regra e coloque-o no mesmo array de regras. Gere um com o Artisan:
php artisan make:rule DeliverableEmail
Mantenha a chave fora do seu código configurando-a através de config/services.php,
que a lê do seu .env:
// config/services.php
'qualisend' => [
'key' => env('QUALISEND_API_KEY'),
],
Então a regra chama o endpoint de verificação com a facade Http, lê o envelope
result e falha apenas quando o veredito é undeliverable:
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Facades\Http;
class DeliverableEmail implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
try {
$response = Http::withToken(config('services.qualisend.key'))
->timeout(10)
->post('https://app.qualisend.com/api/v1/verify', ['email' => $value]);
} catch (ConnectionException $e) {
// A timeout or network error: let the signup through, verify later.
return;
}
// On our own outage or a rate limit, don't block a real signup.
if ($response->failed()) {
return;
}
$result = $response->json('result');
if (($result['status'] ?? null) === 'undeliverable') {
$fail('This email address appears to be undeliverable.');
}
}
}
Http::withToken() define o cabeçalho de autenticação bearer, ->post() envia o
corpo JSON, e $response->json('result') extrai o objeto result aninhado com
acesso por ponto. Um timeout ou erro de conexão lança uma ConnectionException
em vez de devolver uma resposta, então a regra também a captura e deixa o
cadastro passar. A referência da API traz o formato
completo da requisição, e a primeira resposta é lida de volta assim:
{
"job_id": "5b1f3c2a-8d4e-4f6a-9b7c-2e1d0a9f8c71",
"result": {
"status": "unknown",
"score": 61,
"reason": null,
"smtp": "pending",
"sub_flags": { "role": false, "disposable": false, "free_mail": false, "catch_all": false }
}
}
status é um dentre deliverable, risky, undeliverable ou unknown. A regra
acima falha de forma dura apenas em undeliverable e deixa risky e unknown
passarem, para que você decida mais adiante o que fazer com eles — condicionar
pelo score, ou ramificar em uma entrada de sub_flags como disposable — em
vez de transformar um endereço limítrofe em um erro de formulário. Trate o JSON
acima como o formato, não como o contrato; a lista completa de campos vive na
documentação para desenvolvedores.
Esse primeiro status é o veredito instantâneo. Quando result.smtp vem como
"pending", como acima, a sondagem da caixa de entrada ainda está rodando, e o
veredito final chega em GET https://app.qualisend.com/api/v1/jobs/{job_id}
quando ela termina. Não segure o formulário esperando por ele nem chame /verify
de novo: guarde o job_id, consulte-o a partir de um job em fila (fora do
caminho da requisição) e trate ali um resultado undeliverable. Quando smtp
vem como "cached" ou "skipped", o result já é final.
Juntando as camadas para validar um endereço de e-mail no Laravel#
Componha as três em um único form request, o mais barato primeiro, e deixe o
bail parar na primeira falha para que a regra de API só rode em endereços que já
passaram pela sintaxe e pela checagem MX:
namespace App\Http\Requests;
use App\Rules\DeliverableEmail;
use Illuminate\Foundation\Http\FormRequest;
class StoreSubscriberRequest extends FormRequest
{
public function rules(): array
{
return [
'email' => [
'bail',
'required',
'email:rfc,dns',
new DeliverableEmail(),
],
];
}
public function messages(): array
{
return [
'email.email' => 'Enter a valid email address on a real domain.',
];
}
}
bail é o truque todo. Um endereço malformado ou um domínio inativo falha
localmente e nunca gasta um crédito de API; só os endereços que valem a ida e
volta pela rede chegam à camada SMTP. Faça o type-hint do request no seu
controller e o Laravel roda o pipeline antes mesmo de o corpo do seu método
executar:
public function store(StoreSubscriberRequest $request)
{
// Every layer has passed — safe to persist.
$subscriber = Subscriber::create($request->validated());
return redirect()->route('subscribers.index');
}
É a estratificação, não o framework, que torna a validação confiável — o mesmo formato da versão em PHP, com o Laravel colapsando as duas primeiras camadas em uma só regra. Rode o pipeline síncrono rápido no cadastro e reserve a passagem mais lenta, confirmada por SMTP, para o trabalho de bastidores: o guia de cadastro serverless mostra o padrão em tempo real, e como limpar uma lista de e-mails cobre o lado do processamento em lote.
Perguntas frequentes
01A regra email do Laravel verifica se o domínio existe?
Só se você pedir. A regra email simples valida a sintaxe com o pacote egulias/email-validator (o estilo rfc). Adicione o estilo dns - email:rfc,dns - e o Laravel também confirma que o domínio resolve uma rota de e-mail, então um domínio inventado ou inativo falha na mesma passagem. Nenhum dos estilos contata a caixa de entrada, então uma aprovação ainda significa "com cara de entregável", não "entregável".
02Qual é a diferença entre email:rfc, email:filter e email:dns?
rfc valida contra as RFC 5321/5322 via egulias e é o padrão do Laravel. filter executa o filter_var($email, FILTER_VALIDATE_EMAIL) do PHP - exatamente a checagem do guia de PHP - enquanto filter_unicode permite algum Unicode. dns adiciona uma consulta MX ao vivo, e spoof rejeita caracteres homógrafos enganosos. Você os combina com vírgulas, então email:rfc,dns é o portão prático de sintaxe mais domínio.
03Como verifico se uma caixa de entrada existe no Laravel?
Não com uma regra nativa - a confirmação da caixa de entrada exige uma conversa SMTP que você não deveria executar a partir do seu servidor de aplicação, porque a porta 25 é amplamente bloqueada e o resultado depende da reputação do seu IP de envio. Envolva uma API de verificação em um objeto ValidationRule personalizado, chame-a com a facade Http, leia o envelope result e falhe em undeliverable. Coloque-a depois de email:rfc,dns com bail para que ela só rode em endereços que já passaram pelas camadas locais. Se result.smtp vier como pending, a sondagem da caixa de entrada ainda está rodando: leia o veredito final em GET /api/v1/jobs/{job_id} a partir de um job em fila, não dentro do validador.
04A regra dns vai deixar o envio dos meus formulários mais lento?
Pode deixar. O estilo dns realiza uma consulta DNS ao vivo a cada validação, então um resolvedor lento ou inacessível trava a requisição. Tudo bem para um único formulário de cadastro, mas mantenha-o fora de importações em massa, e não o empilhe com uma chamada de API síncrona a menos que você tenha definido um timeout no cliente Http - como a regra DeliverableEmail acima faz com ->timeout(10).
Pronto para adicionar a camada da caixa de entrada? Jogue um endereço no
verificador de e-mail gratuito para ver um endereço
aprovado pela regra voltar como inentregável, e depois conecte o mesmo veredito à
sua ValidationRule com os exemplos de copiar e colar da
referência da API.



