EmailCheckerEmailChecker

Documentação · Node.js

Validar email em Node.js

Node 18+ tem fetch nativo. Não precisa instalar nada — basta chamar a API REST do EmailChecker direto. A validação é em lote: você envia os emails, recebe um id e consulta esse id até o lote fechar. Compatível com qualquer runtime: Node, Bun, Deno, Edge.

Instalação (npm / pnpm / yarn / bun)

bash
# Nenhum pacote externo necessário com Node 18+.
# Para versões anteriores, use undici ou node-fetch.
npm install undici  # opcional, só pra Node <18

Passo 1 · POST /api/v1/batch

Enviar o lote

Endpoint assíncrono. Manda de 1 a 100.000 emails num POST e recebe 201 com o id do lote. O campo name é opcional e serve pra você achar o lote depois.

js
// emailchecker.mjs — Node 18+, só fetch nativo
const API_KEY = process.env.EMAILCHECKER_API_KEY;
const BASE_URL = 'https://app.emailchecker.email/api/v1';

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

async function api(path, init) {
  const r = await fetch(`${BASE_URL}${path}`, {
    ...init,
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      'Content-Type': 'application/json',
    },
    signal: AbortSignal.timeout(30_000),
  });

  // limite: 300 req/min e 10.000/hora por key
  if (r.status === 429) {
    await sleep(60_000);
    return api(path, init);
  }

  const body = await r.json();
  if (!r.ok) throw new Error(`EmailChecker ${r.status}: ${body.error}`);
  return body.data;
}

// POST /api/v1/batch — de 1 a 100.000 emails; "name" é opcional
async function submitBatch(emails, name) {
  const batch = await api('/batch', {
    method: 'POST',
    body: JSON.stringify(name === undefined ? { emails } : { emails, name }),
  });
  return batch.id; // uuid do lote
}

const batchId = await submitBatch(
  ['joao@empresa.com.br', 'contato@outra.com.br'],
  'minha-lista',
);
console.log('lote enviado:', batchId);

Passo 2 · GET /api/v1/batch/{id}

Consultar o resultado

Não enviamos webhook: o jeito de saber que terminou é consultar o lote até status virar completed. Só aí o array emails vem preenchido, com result, score, reason e os atributos isv_*. O limite é de 300 req/min e 10.000/hora por key — um poll a cada 5 segundos sobra.

js
// poll.mjs — não existe webhook: consulte o lote até ele fechar
// GET /api/v1/batch/{id} — 5s entre polls dá ~12 req/min, folgado no limite
async function waitForBatch(id, { intervalMs = 5_000, timeoutMs = 1_800_000 } = {}) {
  const deadline = Date.now() + timeoutMs;

  while (Date.now() < deadline) {
    const batch = await api(`/batch/${id}`);
    if (batch.status === 'completed') return batch;
    if (batch.status === 'failed' || batch.status === 'cancelled') {
      throw new Error(`lote ${batch.status}`);
    }
    await sleep(intervalMs); // pending | processing
  }

  throw new Error('lote não terminou dentro do tempo');
}

const batch = await waitForBatch(batchId);
console.log(`${batch.size} emails, finalizado em ${batch.finished_at}`);

// emails[] só vem preenchido com status === "completed"
for (const e of batch.emails) {
  // result: "deliverable" | "undeliverable" | "risky" | "unknown"
  if (e.result === 'deliverable' && e.score >= 80) {
    console.log(e.email, e.score, e.reason, e.is_free);
  }
}

Boas práticas

Pitfalls

Armadilhas específicas de Node.js

Erros sutis que só aparecem em produção. Conheça antes de subir o código.

fetch nativo não tem timeout — você precisa abortar na mão

Diferente de axios, o fetch do Node não aborta sozinho. Sem signal, uma conexão lenta pendura a request indefinidamente e segura um worker. Use AbortSignal.timeout(30_000) (Node 18+) ou um AbortController com setTimeout e clearTimeout no finally pra não vazar o timer.

r.ok não cobre 4xx vs 5xx — trate os dois diferente

fetch só rejeita em erro de rede; status 4xx/5xx caem no caminho de sucesso com r.ok === false. Um 401/422 (sua key ou payload) não deve ter retry; um 429/5xx deve. Cheque r.status antes de decidir: relançar erro fatal em 4xx, esperar e repetir em 429/5xx.

O body só pode ser lido uma vez

Chamar await r.text() pra logar o erro e depois r.json() lança "body used already". Leia o corpo uma única vez numa variável (const raw = await r.text()) e faça JSON.parse a partir dela, ou clone com r.clone() antes da primeira leitura.

JSON.stringify silencioso com BigInt e undefined

Se os emails vierem de objetos do ORM com campos undefined eles somem do JSON sem aviso, e qualquer BigInt lança TypeError. Monte o payload explícito ({ emails }) com strings puras em vez de stringificar as linhas do banco inteiras.

Mais linguagens

Exemplos em outras linguagens

Comece agora

Pronto pra parar de mandar email pra endereço morto?

Comece grátis com 500 créditos. Sem cartão, sem compromisso.