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

Como validar um endereço de e-mail no Express

9 minutes read

Qualisend team
Uma janela de código Express filtrando um e-mail pelas camadas de validação de sintaxe, MX e caixa postal

Para validar um endereço de e-mail no Express, você monta um middleware, não uma única checagem — e esse middleware precisa responder a três perguntas diferentes, em ordem. O endereço tem o formato correto? O domínio dele consegue receber e-mail? A caixa postal realmente existe? O Express em si não traz nenhuma dessas respostas, mas o ecossistema em torno dele reduz cada uma a poucas linhas: express-validator para a sintaxe, o dns/promises embutido no Node para o domínio e uma API de verificação para a caixa postal. Este guia constrói as três como validadores em uma única rota POST e mostra exatamente onde o framework para e onde uma checagem de verdade começa. É a versão em formato Express do guia de validação de e-mail em Node.js, então todo o raciocínio sobre DNS e SMTP de lá continua valendo por inteiro.

A resposta curta#

Encadeie três validadores no campo email, do mais barato ao mais caro, e deixe o .bail() do express-validator interromper o fluxo assim que um deles for decisivo: use body('email').isEmail() para a sintaxe, um validador personalizado assíncrono envolvendo o resolveMx do dns/promises para o domínio e um segundo validador personalizado assíncrono que chama uma API de verificação para a caixa postal — retornando um 422 quando o veredito é undeliverable. Não tente abrir conexões SMTP a partir do seu processo Express para sondar caixas postais 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 API consegue confirmar um endereço como válido.

Camada 1: sintaxe com express-validator#

O express-validator é o middleware de validação de fato para o Express — um invólucro fino e nativo do Express em torno da biblioteca validator.js. A checagem isEmail() dele é o isEmail do validator.js, então você ganha o mesmo parser de sintaxe testado em batalha em que todo projeto Node já se apoia, exposto como um middleware encadeável. Declare uma regra no campo e ela roda antes do seu handler:

import { body } from "express-validator";

const validateEmail = body("email")
  .trim()
  .isEmail()
  .withMessage("Enter a valid email address.")
  .bail();

O .trim() sanitiza os espaços em branco no início e no fim, o .isEmail() valida o formato, o .withMessage() define o texto do erro e o .bail() para a cadeia desse campo no instante em que a sintaxe falha — de modo que as camadas mais caras abaixo nunca rodem sobre uma string malformada. Esse .bail() é o equivalente, no Express, a parar na checagem mais barata possível.

Saiba exatamente o que isso te dá. O isEmail() lê a string — ele nunca resolve DNS e nunca abre um socket. definitely-fake@gmail.com passa. info@company-that-folded.com passa. typo@gmial.com passa. Os três são não entregáveis, e nenhum validador que apenas inspeciona a string jamais vai te avisar disso — pela mesma razão que 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 consegue receber e-mail?#

Essa é a primeira camada que o Express não te entrega de bandeja, e é barata de acoplar. Um domínio sem registros MX não consegue aceitar e-mail para ninguém, então uma única consulta de DNS elimina domínios mortos, nomes de empresa escritos errado e TLDs inventados antes que você gaste qualquer coisa na rede. O node:dns/promises, embutido no Node, resolve registros MX sem nenhuma dependência, e o .custom() do express-validator aceita uma função assíncrona — lance um erro dentro dela para reprovar o campo, retorne true para aprovar:

import { resolveMx } from "node:dns/promises";

async function hasMailRoute(email) {
  const domain = email.slice(email.lastIndexOf("@") + 1);

  let records = [];
  try {
    records = await resolveMx(domain);
  } catch {
    // ENOTFOUND / ENODATA — the domain doesn't exist or publishes no MX
    throw new Error("This domain cannot receive email.");
  }

  if (records.length === 0) {
    throw new Error("This domain cannot receive email.");
  }
  return true;
}

Como isso é uma simples função assíncrona, ela encaixa direto na cadeia como um validador personalizado, protegida por outro .bail() para que a camada de caixa postal só rode em um domínio que de fato resolve uma rota de e-mail:

body("email")
  .isEmail()
  .bail()
  .custom(hasMailRoute)
  .bail();
await hasMailRoute("jane@gmail.com");               // true
await hasMailRoute("jane@company-that-folded.com"); // throws — no MX

Alguns domínios aceitam e-mail em um registro A sem MX (MX implícito). Se você quiser respeitar esse caso extremo, recorra ao resolve4 quando o resolveMx voltar vazio — mas, para a esmagadora maioria dos endereços reais, uma checagem de MX é o filtro certo.

Camada 3: a caixa postal realmente existe?#

As camadas 1 e 2 só conseguem eliminar 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 é sintaxe válida em uma rota de e-mail ativa, e ainda assim é uma caixa postal que nunca foi criada. Confirmar uma caixa postal específica significa a conversa de entrega SMTP: conectar-se 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 Express com o módulo net. Na prática, você não deveria rodar isso a partir do servidor da sua 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ê se conecta, e os servidores receptores aplicam greylisting e limitam a taxa de remetentes desconhecidos — de modo que uma sondagem que funciona em um teste local silenciosamente falha, ou te coloca em listas de bloqueio, em produção. O funcionamento da verificação de e-mail percorre toda a pipeline, incluindo domínios catch-all. Essa é a camada que vale a pena delegar.

Chamando o Qualisend a partir de um validador personalizado assíncrono#

A delegação é apenas mais um validador personalizado. O endpoint de verificação do Qualisend roda a pipeline inteira — sintaxe, DNS e a sondagem SMTP da caixa postal — a partir de uma infraestrutura com reputação gerenciada e devolve um veredito. Chame-o com o fetch global (embutido no Node 18+), leia sua chave a partir de uma variável de ambiente e lance um erro apenas quando o veredito for undeliverable:

async function isDeliverable(email) {
  const res = await fetch("https://api.qualisend.com/v1/verify", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.QUALISEND_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ email }),
  });

  // On our own outage or a rate limit, don't block a real signup.
  if (!res.ok) return true;

  const { result } = await res.json();

  if (result.status === "undeliverable") {
    throw new Error("We couldn't confirm a mailbox at this address.");
  }
  return true;
}

O endpoint acima é um espaço reservado — consulte a referência da API para a URL base exata e o formato da requisição — mas a resposta volta como um envelope { "result": { ... } }:

{
  "result": {
    "status": "deliverable",
    "score": 95,
    "reason": null,
    "sub_flags": { "role": false, "disposable": false, "free": false, "catch_all": false }
  }
}

O status é um entre deliverable, risky, undeliverable ou unknown. O validador acima reprova de forma definitiva apenas o undeliverable e deixa passar risky e unknown, para que você decida mais adiante o que fazer com eles — barrar com base no score ou ramificar por 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 está na documentação para desenvolvedores.

Juntando as camadas para validar um endereço de e-mail no Express#

Componha as três em uma única cadeia body("email"), do mais barato ao mais caro, com .bail() entre cada uma para que uma falha interrompa o fluxo antes que a próxima camada rode. O validador da API só dispara em um endereço que já passou pela sintaxe e pela checagem de MX:

import { body } from "express-validator";

const validateEmail = [
  body("email")
    .trim()
    .isEmail()
    .withMessage("Enter a valid email address.")
    .bail()
    .custom(hasMailRoute)
    .bail()
    .custom(isDeliverable),
];

Coloque esse array na frente do seu handler de rota e depois leia os erros coletados com o validationResult. Quando a cadeia falha, responda com um 422 e o array de erros; caso contrário, todas as camadas passaram e o endereço está seguro para persistir:

import express from "express";
import { validationResult } from "express-validator";

const app = express();
app.use(express.json());

app.post("/signup", validateEmail, (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(422).json({ errors: errors.array() });
  }

  // Every layer passed — safe to create the account.
  const { email } = req.body;
  return res.status(201).json({ email });
});

Essa ordem é o truque inteiro: o isEmail() corta o lixo antes que seu código rode, a checagem local de MX descarta domínios mortos de graça, e a API é acionada apenas para endereços que passaram pelas duas. Uma validação na ordem errada — ou que pula as camadas baratas — gasta um crédito de API a cada erro de digitação. É a estratificação, não o framework, que torna a validação confiável; é o mesmo formato da versão em Node.js, com a cadeia de middleware do Express fazendo o papel da pipeline.

Uma decisão a tomar logo de cara: o que acontece quando a própria chamada da API falha. Um timeout de rede ou um fetch que lança um erro dentro do isDeliverable não deveria entregar a um cliente real um 422 que ele não consegue corrigir. O validador acima já suaviza esse caso ao retornar true diante de uma resposta não OK — tratando um serviço inacessível como unknown em vez de undeliverable, deixando o cadastro passar e adiando a reverificação para depois. As camadas de sintaxe e de MX já rodaram localmente, então você só está relaxando a camada que depende da rede.

Rode essa pipeline síncrona e rápida no ponto de coleta e reserve a verificação em lote, mais pesada, para a limpeza de listas. O guia de cadastro serverless mostra o padrão em tempo real de ponta a ponta, e, na hora de escolher qual serviço fica por trás da camada três, a comparação de APIs coloca as opções lado a lado.

Perguntas frequentes#

O isEmail do express-validator é suficiente para validar um endereço de e-mail?#

Para a sintaxe, sim — body("email").isEmail() encapsula o isEmail do validator.js e é a checagem certa para a primeira camada, muito 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 ser entregue". Combine-o com uma consulta de MX e uma checagem de caixa postal antes de confiar no endereço.

Como escrevo um validador personalizado assíncrono no Express?#

Passe uma função assíncrona para o .custom() do express-validator. A função recebe o valor do campo; retorne true (ou resolva) para aprovar e throw um Error (ou rejeite) para reprovar — a mensagem lançada vira o erro de validação. É assim que as funções hasMailRoute e isDeliverable acima se encaixam na mesma cadeia body("email"). Coloque um .bail() antes de cada uma para que uma camada reprovada interrompa a cadeia em vez de executar a próxima.

Dá para verificar se uma caixa postal existe no Express sem uma API?#

Em parte. O node:dns/promises confirma que o domínio aceita e-mail, o que descarta domínios mortos de graça e não exige nenhuma dependência. Confirmar a caixa postal exige uma conversa SMTP que você pode tentar com o módulo net, mas não deveria rodar a partir do servidor da sua 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; o que é a verificação de e-mail cobre os termos.

Devo retornar um 422 ou deixar o cadastro passar quando o resultado é desconhecido?#

Rejeite undeliverable com um 422 — é um endereço comprovadamente ruim que o usuário pode corrigir. Mas não reprove por causa de uma falha do seu próprio serviço de verificação: trate uma resposta não OK, um timeout ou um status unknown como "deixa passar e revalida depois", para que um problema de rede passageiro nunca bloqueie um cadastro real. O validador isDeliverable faz exatamente isso ao retornar true quando a resposta não é OK.


Pronto para adicionar a camada de caixa postal? Jogue um endereço sintaticamente perfeito no verificador de e-mail gratuito para ver uma string aprovada pelo isEmail voltar como undeliverable, e depois conecte o mesmo veredito ao seu middleware Express com os exemplos de copiar e colar da referência da API.

Your reputation, protected.

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

Get started