Por Que Você Não Deveria Validar E-mails com Regex
Por que a regex de e-mail falha: rejeita endereços válidos, aceita os que não entregam e pode causar ReDoS. Use o input email do HTML5 ou uma biblioteca.
Uma regex não consegue validar um endereço de e-mail — ela só verifica a forma geral, e mesmo o padrão mais cuidadosamente elaborado tanto rejeitará endereços legítimos quanto aceitará endereços sintaticamente plausíveis que jamais entregarão mensagens. O problema não é que você ainda não encontrou o padrão certo; é que a pergunta “este e-mail é válido?” confunde três problemas distintos, e uma regex só consegue tocar em um deles — o menos útil. Este artigo separa esses três problemas, cita o que as especificações realmente dizem (RFC 5321, RFC 5322, RFC 6531 e o WHATWG HTML Living Standard), mostra exatamente onde padrões populares falham em ambas as direções e fornece o código pragmático em JS/TS para usar no lugar.
Principais Conclusões
- A validação de e-mail tem três camadas distintas — uma verificação de sanidade para UX, validação sintática contra RFC 5321/5322 e verificação de existência — e apenas um e-mail de confirmação prova que o endereço realmente recebe mensagens.
- O
<input type="email">do HTML Living Standard usa uma regex que a própria especificação classifica como uma “violação intencional do RFC 5322”; ela deliberadamente não é RFC-completa e é um padrão melhor do que qualquer expressão que você escreva manualmente. - Regexes populares para e-mail falham em ambas as direções: rejeitam endereços reais (plus-addressing, novos gTLDs, partes locais entre aspas, endereços internacionalizados conforme RFC 6531) e aceitam endereços que não podem ser entregues.
- Uma regex propensa a backtracking executada no servidor em Node.js pode ser explorada com uma entrada curta e especialmente elaborada para bloquear o event loop — um vetor de negação de serviço ReDoS (CWE-1333).
- A única restrição de comprimento que vale a pena impor antes de qualquer padrão ser executado vem do RFC 5321 §4.5.3.1.3: o endereço em si é limitado a 254 caracteres.
As três camadas da validação de e-mail
A validação de e-mail tem três camadas distintas: uma verificação de sanidade para UX (parece um e-mail?), validação sintática (está em conformidade com as regras do RFC 5321/5322?) e verificação de existência (essa caixa de correio realmente recebe mensagens?). Apenas a terceira camada prova que o endereço funciona, e apenas um e-mail de confirmação a entrega. As referências que dizem para “parar de usar regex” estão certas, mas confundem essas camadas. Mantê-las separadas é o que indica qual ferramenta pertence a cada lugar.
- Camada 1 — Verificação de sanidade para UX. Uma verificação barata, rápida e do lado do cliente que detecta erros de digitação óbvios (
alicegmail.com, um espaço no final) e fornece feedback imediato. Esta é a única camada onde uma regex tem lugar, e mesmo aqui você quer o menor padrão que faça o trabalho. - Camada 2 — Validação sintática. A string está em conformidade com a gramática dos RFCs de e-mail? Isso é muito mais difícil do que parece, supera regexes escritas manualmente e — de forma crítica — não prova nada sobre a capacidade de entrega. Um endereço perfeitamente em conformidade com o RFC pode apontar para um domínio que não existe.
- Camada 3 — Verificação de existência. Uma caixa de correio real recebe mensagens neste endereço? A única prova de que um endereço de e-mail funciona é uma mensagem entregue com sucesso; um e-mail de confirmação faz em uma etapa o que nenhuma regex consegue fazer.
O erro que quase todo “regex definitivo para e-mail” comete é tentar fazer a Camada 2 perfeitamente, quando a Camada 2 não responde à pergunta que alguém realmente se importa. A pergunta é da Camada 3, e nenhum padrão a alcança.
O que “válido” realmente significa
Discover how at OpenReplay.com.
Um endereço de e-mail válido é muito mais permissivo do que a maioria das regexes assume, porque a gramática no RFC 5321 (SMTP) e no RFC 5322 (o formato da mensagem) permite construções que parecem incorretas. A parte local — tudo antes do @ — pode conter uma longa lista de caracteres especiais e pode até ser uma string entre aspas.
A parte local sem aspas é construída a partir de atext, definido no RFC 5322 §3.2.3, que permite estes caracteres junto com letras e dígitos:
! # $ % & ' * + - / = ? ^ _ ` { | } ~
Isso significa que user+tag@example.com (plus-addressing) é válido — o + é um atext comum, conforme o RFC 5321 §4.1.2. Se o servidor receptor trata o +tag como um subendereço é específico da implementação (RFC 5233), mas o endereço em si é bem formado. A parte local também pode ser uma string entre aspas: "user name"@example.com é válido conforme o RFC 5321 §4.1.2 e o RFC 5322 §3.2.4, espaços e tudo mais. O domínio pode ser um endereço IP literal entre colchetes — user@[192.168.1.1] é válido conforme o RFC 5321 §4.1.3.
Há uma restrição que vale a pena impor de forma simples. O RFC 5321 §4.5.3.1.3 limita o forward-path a 256 octetos incluindo os colchetes angulares, o que deixa 254 caracteres para o endereço em si; a parte local é limitada a 64 octetos (§4.5.3.1.1) e o domínio a 255 (§4.5.3.1.2). Uma verificação de comprimento é a única validação que uma comparação de strings trata corretamente e que uma regex não precisa fazer.
Endereços internacionalizados (EAI)
Endereços de e-mail internacionalizados definidos no RFC 6531 — como 用户@例子.广告 — são válidos e cada vez mais comuns; nenhuma regex exclusivamente ASCII os trata corretamente, e isso é um problema de biblioteca, não de regex. O EAI (RFC 6531 §3.3) estende a parte local para permitir UTF-8, e o domínio pode ser Unicode não-ASCII. Isso é distinto dos domínios codificados em punycode IDNA (RFC 5891): o EAI cobre também a parte local. Qualquer padrão que assume [a-zA-Z0-9] para a parte local está errado para uma fatia crescente dos usuários do mundo, e não existe uma única regex que aceite corretamente tanto partes locais ASCII quanto Unicode sem também aceitar entradas inválidas.
Por que a regex de validação de e-mail falha em ambas as direções
Uma regex de e-mail escrita manualmente falha tanto como porteiro quanto como filtro: produz falsos negativos (rejeitando endereços entregáveis) e falsos positivos (aceitando endereços que estão em conformidade com a gramática, mas que jamais receberão mensagens). Ambos os modos de falha chegam à produção constantemente porque o conjunto de testes usa test@example.com, que passa em todos os padrões.
Considere o padrão canônico copiado do Stack Overflow — um padrão que exige um TLD de 2 a 4 caracteres:
// Um padrão comumente copiado. Não use isso.
const bad = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/;
Veja o que ele faz com endereços reais:
| Endereço | O que testa | Esta regex | Resultado correto | Por que está errado |
|---|---|---|---|---|
name+filter@gmail.com | plus-addressing | ✅ aceita | ✅ válido | (passa aqui, mas padrões mais rígidos rejeitam +) |
user@studio.photography | gTLD longo | ❌ rejeita | ✅ válido | {2,4} rejeita TLDs com mais de 4 caracteres |
"user name"@example.com | parte local entre aspas | ❌ rejeita | ✅ válido | strings entre aspas e espaços são válidos |
用户@例子.广告 | EAI (RFC 6531) | ❌ rejeita | ✅ válido | classes de caracteres exclusivamente ASCII |
someone@validformat.test | domínio inexistente | ✅ aceita | ❌ não entregável | a sintaxe está correta; o domínio não resolve |
Uma regex que rejeita name+filter@gmail.com (plus-addressing) ou user@studio.photography (um gTLD delegado pelo Programa de Novos gTLDs da ICANN, com .photography adicionado à raiz em 2013) não está sendo rigorosa — está sendo incorreta. Ambos são endereços sintaticamente plausíveis usando recursos válidos de e-mail. A restrição {2,4} do TLD por si só quebra .photography, .accountants, .engineering e centenas de outras delegações válidas.
Replays de sessão frequentemente revelam usuários encontrando erros de validação, corrigindo sua entrada várias vezes e abandonando o formulário. Estudos de usabilidade de formulários identificaram consistentemente o atrito de validação como um fator contribuinte para o abandono e a redução das taxas de conversão.
Falsos positivos — endereços que passam na regex mas nunca são entregues — são igualmente reais. someone@validformat.test passa no padrão acima e na maioria dos outros, mas .test é um TLD reservado (RFC 2606) que jamais entregará mensagens. Conformidade sintática e capacidade de entrega são propriedades independentes, e uma regex só vê a primeira.
ReDoS: quando a regex é a vulnerabilidade
Uma regex propensa a backtracking executada no servidor em Node.js pode ser explorada com uma entrada elaborada para bloquear o event loop — um vetor de negação de serviço (CWE-1333: Complexidade Ineficiente de Expressão Regular e a referência OWASP ReDoS) que não tem nada a ver com e-mail e tudo a ver com backtracking catastrófico. Padrões com quantificadores aninhados ou adjacentes sobre classes de caracteres sobrepostas podem levar tempo exponencial em entradas que quase correspondem.
Aqui está uma demonstração reproduzível. O (...)+ do padrão envolve um grupo que pode corresponder ao mesmo caractere de múltiplas formas, então uma longa sequência de um caractere seguida de um caractere que não corresponde força o motor a tentar exponencialmente muitas partições antes de falhar:
// Node.js v24. Execute com: node redos.js
// Um padrão deliberadamente vulnerável, propenso a backtracking.
const evil = /^([a-zA-Z0-9]+)*@example\.com$/;
// Uma entrada quase correspondente elaborada: muitos 'a's, depois um caractere que quebra a correspondência.
const attack = "a".repeat(40) + "!";
console.time("redos");
evil.test(attack); // bloqueia o event loop
console.timeEnd("redos");
Em uma versão atual do Node.js, aumentar a contagem de repetições faz o tempo de correspondência crescer de forma explosiva — cada caractere adicionado aproximadamente dobra o trabalho. Como o motor de regex do Node executa de forma síncrona na thread principal, uma única requisição carregando essa entrada paralisa o event loop e bloqueia todas as outras requisições em andamento. A estrutura (x+)* é o sinal de alerta: qualquer grupo que pode corresponder à mesma substring de mais de uma forma, sob um quantificador externo, é candidato a backtracking catastrófico. A solução não é um padrão mais inteligente — é não construir essa classe de padrão, o que é exatamente o que se obtém ao delegar para a plataforma ou uma biblioteca mantida ativamente.
Sintaxe não é capacidade de entrega
Mesmo um endereço perfeitamente em conformidade com o RFC não diz nada sobre se a mensagem chegará. Uma regex não consegue verificar se o domínio existe, se ele tem registros MX, se a caixa de correio está provisionada ou se o endereço não é um descartável temporário. Essas são questões de rede e política, não de gramática. Um endereço como realuser@gmail.com e um realuser@gmial.com digitado incorretamente são ambos sintaticamente válidos; apenas uma consulta DNS os distingue, e apenas uma entrega real distingue uma caixa de correio ativa de uma inativa.
Domínios de e-mail descartáveis e temporários são uma preocupação relacionada, porém separada: endereços que são sintaticamente e operacionalmente válidos, mas existem para contornar seu cadastro. Detectá-los requer uma lista de bloqueio mantida ativamente dos domínios provedores, não um padrão — a lista de domínios muda constantemente, e qualquer lista que você codifique diretamente ficará desatualizada. Trate isso como uma camada de política sobre a validação, não como parte dela.
O que fazer no lugar
Use a abordagem em camadas: uma verificação mínima de sanidade para UX, a validação integrada da plataforma para sintaxe, uma biblioteca mantida apenas quando precisar de mais, e um e-mail de confirmação para a única coisa que realmente importa. Aqui está a ordem, da mais barata à mais autoritativa.
1. Uma verificação mínima de sanidade
Para feedback imediato do lado do cliente, o menor padrão útil é o do argumento original “pare de validar com regex”: exija algo, um @, algo, um ponto e algo. Combine-o com uma verificação de comprimento.
/**
* Verificação de sanidade da Camada 1: detecta erros de digitação óbvios, nada mais.
* Deliberadamente permissivo — NÃO é prova de validade.
* @param value - a string de entrada bruta
* @returns true se o valor tem a forma aproximada de um e-mail e tem <= 254 caracteres
*/
export function looksLikeEmail(value: string): boolean {
if (value.length > 254) return false; // RFC 5321 §4.5.3.1.3
return /.+@.+\..+/.test(value);
}
Esta verificação de sanidade rejeita alicegmail.com e alice@localhost, aceita plus-addressing e gTLDs longos, e executa em tempo constante. Não é seguro tratar seu retorno true como “válido” — é apenas um detector de erros de digitação.
2. Prefira a plataforma: <input type="email">
O melhor padrão para validação sintática é o próprio <input type="email"> do navegador, e vale a pena saber exatamente o que ele faz. O <input type="email"> do HTML Living Standard usa uma regex que a própria especificação chama de “violação intencional do RFC 5322” — ela intencionalmente não é RFC-completa, trocando precisão da especificação por usabilidade, e é um padrão melhor do que qualquer expressão que você escreva por conta própria. A especificação cita o padrão exato:
^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$
Lendo cláusula por cláusula:
[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+— a parte local, permitindo os caracteres especiaisatext. Deliberadamente não suporta partes locais entre aspas ("user name"@…).@— exatamente um separador.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?— um rótulo de domínio: começa e termina com alfanumérico, hífens permitidos no interior, limitado a 63 caracteres. Deliberadamente não suporta domínios IP-literal (user@[192.168.1.1]).(?:\.<rótulo>)*— zero ou mais rótulos adicionais separados por ponto, então domínios de rótulo único e múltiplo passam.
O WHATWG documenta abertamente o tradeoff: este padrão rejeita alguns endereços RFC 5322 tecnicamente válidos (partes entre aspas, literais IP) de propósito, porque essas formas são extremamente raras em cadastros reais e suportá-las introduz mais bugs do que previne. Esse é o tradeoff correto para um campo de formulário, e é por isso que <input type="email"> deve ser sua referência de Camada 2 — ele não tem patologia de backtracking e corresponde ao que os navegadores já impõem.
3. Recorra a uma biblioteca mantida apenas quando precisar de mais
Se você precisar de validação sintática do lado do servidor além do padrão HTML5, use uma biblioteca mantida e bem testada em vez de criar a sua própria. O pacote validator (npm validator, licença MIT) expõe uma função isEmail que suporta partes locais entre aspas e fornece opções para domínios IP-literal e nomes de exibição:
import isEmail from "validator/lib/isEmail";
/**
* Validação sintática da Camada 2, do lado do servidor.
* @param email - endereço candidato (já verificado quanto ao comprimento)
* @returns true se sintaticamente válido conforme as regras alinhadas ao RFC do validator
*/
export function isSyntacticallyValid(email: string): boolean {
return isEmail(email, { allow_utf8_local_part: true });
}
Prefira este ao pacote mais antigo email-validator, que não é publicado desde 2018. Uma biblioteca oferece tratamento testado de casos extremos e um mantenedor ativo corrigindo os casos que seu padrão escrito manualmente nunca tratará — incluindo, com as opções corretas, endereços EAI.
4. A resposta real: envie um e-mail de confirmação
A única etapa que prova que um endereço funciona é a entrega. Envie uma mensagem de confirmação com um link de uso único; trate o endereço como verificado apenas após o usuário clicar nele. Isso é double opt-in, e torna a validação elaborada anterior redundante — um endereço malformado ou não entregável simplesmente nunca confirma.
/**
* Esboço do fluxo de verificação. Armazenamento e mailer são específicos da aplicação.
* @param email - uma string que já passou pelas verificações de comprimento e sintaxe
*/
async function startEmailVerification(email: string): Promise<void> {
const token = crypto.randomUUID();
await storePendingVerification(email, token); // expira após, por exemplo, 24h
const link = `https://app.example.com/verify?token=${token}`;
await sendMail(email, "Confirme seu e-mail", `Clique para confirmar: ${link}`);
// Marque a conta como verificada apenas quando /verify for acessado com um token válido.
}
Enviar um e-mail de confirmação é a estrutura à qual fonte após fonte eventualmente chega, pela mesma razão: ela faz em uma etapa o que nenhuma regex consegue fazer. Como Jamie Zawinski disse: “Algumas pessoas, quando confrontadas com um problema, pensam: ‘Eu sei, vou usar expressões regulares.’ Agora elas têm dois problemas.” Para e-mail, o segundo problema é que a regex ainda não respondeu à pergunta.
Conclusão
Pare de tentar validar o endereço e comece a tentar verificar a caixa de correio. Use um padrão mínimo mais um limite de 254 caracteres para feedback imediato de UX, apoie-se no <input type="email"> ou em uma biblioteca mantida como validator para sintaxe, e proteja cada conta real com um e-mail de confirmação — essa etapa final é a única que prova que há alguém do outro lado. Na próxima vez que um formulário de cadastro precisar de um campo de e-mail, recorra à plataforma e ao fluxo de confirmação, não ao padrão do Stack Overflow.
Perguntas Frequentes
Qual é o comprimento máximo válido de um endereço de e-mail?
Um endereço de e-mail é limitado a 254 caracteres. Isso deriva da seção 4.5.3.1.3 do RFC 5321, que limita o forward-path a 256 octetos incluindo os colchetes angulares ao redor, deixando 254 para o endereço em si. A parte local é separadamente limitada a 64 octetos e o domínio a 255 octetos. Uma simples comparação de comprimento impõe isso corretamente, sendo a única validação que vale a pena fazer antes de qualquer padrão ser executado.
O input de e-mail do HTML5 valida contra a gramática completa do RFC 5322?
Não. O HTML Living Standard descreve explicitamente sua regex de input de e-mail como uma 'violação intencional do RFC 5322'. Ele deliberadamente rejeita formas tecnicamente válidas como partes locais entre aspas ('user name'@example.com) e domínios IP-literal (user@[192.168.1.1]) porque esses casos são extremamente raros em cadastros reais. O tradeoff favorece a usabilidade em detrimento da completude da especificação, o que o torna um padrão mais seguro do que um padrão escrito manualmente, mas não é um validador RFC completo.
Como uma regex de validação de e-mail pode causar um ataque de negação de serviço?
Uma regex com quantificadores aninhados ou adjacentes sobre classes de caracteres sobrepostas, como a estrutura ([a-zA-Z0-9]+)*, pode levar tempo exponencial em entradas que quase correspondem. Isso é backtracking catastrófico, classificado como CWE-1333. Executada no servidor em Node.js, onde o motor de regex executa de forma síncrona na thread principal, uma única requisição elaborada pode paralisar o event loop e bloquear todas as outras requisições em andamento. A solução é evitar completamente essa classe de padrão, não escrever um padrão mais inteligente.
Uma regex pode verificar se um endereço de e-mail realmente existe?
Não. Uma regex apenas inspeciona a forma da string; ela não consegue verificar se o domínio existe, tem registros MX ou se a caixa de correio está provisionada. Conformidade sintática e capacidade de entrega são propriedades independentes. Um endereço como realuser@gmial.com é sintaticamente válido, mas não entregável devido a um erro de digitação, e someone@validformat.test passa na maioria dos padrões, mas usa um TLD reservado que nunca entrega mensagens. Apenas um e-mail de confirmação entregue com sucesso prova que um endereço recebe mensagens.
Por que regexes de e-mail rejeitam endereços válidos como name+filter@gmail.com?
O plus-addressing é totalmente válido porque o sinal de adição é um atext comum conforme a seção 3.2.3 do RFC 5322 e a seção 4.1.2 do RFC 5321. Padrões que o rejeitam, assim como endereços em gTLDs longos como .photography ou endereços internacionalizados definidos no RFC 6531, não estão sendo rigorosos — estão sendo incorretos. Esses falsos negativos chegam à produção porque os conjuntos de testes usam test@example.com, que passa em todos os padrões, então a rejeição de endereços reais nunca aparece nos testes.