Como integrar validação de email com n8n (tutorial)
Workflow n8n que envia o lote pra API do EmailChecker, aguarda com Wait + IF e roteia cada lead pelo resultado — sem escrever uma linha de código.
Se você trabalha com RevOps ou operações de marketing, já deve ter sentido o custo de um CRM cheio de emails inválidos: bounce rate alto, deliverability comprometida, SDRs perdendo tempo qualificando contatos que nunca vão responder porque o endereço simplesmente não existe.
A combinação entre n8n e EmailChecker resolve isso na entrada do pipeline — antes de qualquer dado sujo chegar ao HubSpot, Salesforce ou à fila de cadência.
Por que n8n + EmailChecker
O n8n é um orquestrador de workflows self-hosted (ou cloud) que conecta qualquer API via nodes visuais. Ele é particularmente forte em cenários de ops porque permite lógica condicional, retry nativo e tratamento de erro sem depender de Zapier ou Make para cada integração.
E tem um detalhe que faz do n8n a ferramenta que melhor se encaixa aqui: a API do EmailChecker é assíncrona e trabalha em lote. Você envia os emails em POST /api/v1/batch, recebe um id na hora, e consulta GET /api/v1/batch/{id} até o status virar completed. Não existe endpoint de validação unitária síncrona, e a plataforma não chama o seu servidor de volta quando termina — quem descobre que o lote fechou é o polling.
Isso significa que o workflow precisa de um nó de espera e de um loop. O n8n tem os dois (Wait e IF religado no Wait), então dá para montar o fluxo inteiro sem código:
Webhook → HTTP Request (POST /api/v1/batch) → Wait → HTTP Request (GET /api/v1/batch/{id}) → IF (status = completed?) → Switch (result) → [destinos]
O caso de uso real que vamos construir: um formulário de captura de leads envia um webhook para o n8n, o workflow valida o email em segundo plano, e o resultado determina o destino — CRM para leads bons, fila de revisão manual para risky, supressão para undeliverable e unknown.
Pré-requisitos
- n8n rodando (self-hosted via Docker ou conta n8n Cloud)
- Chave de API do EmailChecker no formato
ec_live_...— a API é exclusiva do plano Gold; a referência completa está na documentação da API REST - Acesso ao CRM de destino (HubSpot, Salesforce, Pipedrive — qualquer um com node ou HTTP Request)
- Opcional: workspace Slack para notificações de revisão manual
Passo 1: Criar o workflow novo
Abra o n8n e clique em New Workflow. Dê um nome descritivo, por exemplo Lead Validation — EmailChecker. Ative o modo de edição e mantenha o workflow desativado por enquanto — você vai ativar só depois de testar.
Salve o workflow vazio antes de adicionar nodes. O n8n salva estado localmente e perder configuração de node por acidente é frustrante.
Passo 2: Webhook trigger
Adicione o node Webhook como trigger. Configure:
- HTTP Method: POST
- Path:
/validate-lead(ou qualquer slug que faça sentido no seu contexto) - Response Mode:
Immediately— o n8n responde200assim que o workflow começa, e a validação continua em segundo plano
Esse ponto é importante e define toda a arquitetura: como a validação leva de segundos a minutos, o formulário não pode esperar o resultado. Ele recebe a confirmação de recebimento na hora, e o veredito chega depois no CRM. Para feedback imediato ao usuário, use uma checagem de formato no client (regex) — ela pega o dedo escorregando no teclado, que é 90% do erro de digitação real.
Anote a URL de produção gerada. Ela tem o formato https://seu-n8n.dominio.com/webhook/validate-lead. Essa é a URL que o formulário ou sistema upstream vai chamar.
O body esperado no POST:
{
"email": "contato@empresa.com",
"name": "Maria Silva",
"source": "landing-page-produto-x"
}
O campo email é obrigatório para o próximo passo. Os demais são passados adiante para o CRM.
Passo 3: HTTP Request para enviar o lote
Adicione um node HTTP Request conectado ao Webhook e renomeie para Enviar lote. Essa é a chamada que cria o lote no EmailChecker.
Configure:
- Method: POST
- URL:
https://app.emailchecker.email/api/v1/batch - Authentication: Generic Credential Type → Header Auth
- Name:
Authorization - Value:
Bearer ec_live_SUA_CHAVE— crie como credencial do n8n, nunca hardcode no node
- Name:
- Send Body: ativado, Body Content Type: JSON, Specify Body: Using JSON
O JSON do body (repare que mesmo um único email vai dentro de um array — o endpoint aceita de 1 a 100.000 por chamada):
{
"emails": ["{{ $json.body.email }}"],
"name": "n8n-lead-form"
}
O node completo para colar direto no canvas do n8n (Ctrl+V no canvas importa):
{
"nodes": [
{
"parameters": {
"method": "POST",
"url": "https://app.emailchecker.email/api/v1/batch",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"emails\": [\"{{ $json.body.email }}\"],\n \"name\": \"n8n-lead-form\"\n}",
"options": {
"timeout": 10000
}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [220, 0],
"name": "Enviar lote"
}
],
"connections": {}
}
Depois de colar, abra o node e selecione a credencial de Header Auth que você criou — credencial não viaja no clipboard.
A resposta 201 traz o identificador do lote:
{
"data": {
"id": "8f2c1b7e-4a9d-4c31-9f0e-2b6d5a7c1e34",
"name": "n8n-lead-form",
"created_at": "2026-06-01T14:02:11.000Z",
"user_id": "3d1a…",
"size": 1
}
}
Guarde data.id — é a única chave para recuperar o resultado depois. Se o seu banco ou CRM tem um campo livre no lead, escreva o id ali antes de seguir: se a execução do n8n morrer no meio, você ainda consegue buscar o lote manualmente.
Passo 4: Wait
Adicione um node Wait conectado ao "Enviar lote":
- Resume: After Time Interval
- Wait Amount:
30 - Wait Unit: Seconds
Trinta segundos é um bom ponto de partida para lotes pequenos. Não coloque 1 ou 2 segundos achando que fica mais rápido: o lote leva o tempo que leva, e polling agressivo só queima o seu rate limit (300 requisições/minuto e 10.000/hora por chave).
Passo 5: HTTP Request para consultar o lote
Adicione outro node HTTP Request depois do Wait e renomeie para Consultar lote:
- Method: GET
- URL:
https://app.emailchecker.email/api/v1/batch/{{ $('Enviar lote').first().json.data.id }} - Authentication: a mesma credencial Header Auth do passo 3
Use .first() e não .item aqui. Dentro de um loop, o n8n às vezes não consegue parear o item da execução atual com o item do node anterior e devolve o erro "Can't determine which item to use" — .first() não tem esse problema porque não depende do pareamento.
A resposta:
{
"data": {
"id": "8f2c1b7e-4a9d-4c31-9f0e-2b6d5a7c1e34",
"status": "processing",
"created_at": "2026-06-01T14:02:11.000Z",
"finished_at": null,
"size": 1,
"emails": []
}
}
O array emails só vem preenchido quando status é completed. Enquanto o lote está em pending ou processing, ele volta vazio — por isso o próximo node existe.
Passo 6: IF — o loop de polling
Adicione um node IF conectado ao "Consultar lote":
- Value 1:
{{ $json.data.status }} - Operation: String → is equal to
- Value 2:
completed
Agora a parte que fecha o loop: conecte a saída false de volta no node Wait. O fluxo fica girando Wait → Consultar → IF até o lote terminar, e só então segue pela saída true.
Dois cuidados para esse loop não virar um moinho eterno:
Trate failed e cancelled. São status terminais — o lote não vai mudar mais. Antes de religar no Wait, coloque um segundo IF com a condição booleana:
{{ ['failed', 'cancelled'].includes($json.data.status) }}
Se true, mande para um node de alerta (Slack para o time de ops). Se false — ou seja, pending ou processing — aí sim volta para o Wait.
Coloque um teto de iterações. O n8n expõe $runIndex, que conta quantas vezes o node já rodou nessa execução. Uma condição {{ $runIndex > 60 }} no mesmo IF de erro corta o loop depois de ~30 minutos de espera e evita execução pendurada para sempre.
Passo 7: Switch pelo result
Com o lote concluído, o item que sai da saída true do IF contém a lista de resultados em data.emails. Como estamos validando um lead por vez, o resultado que interessa é data.emails[0].
Adicione um node Switch no modo Rules:
| Regra | Campo | Operador | Valor | Saída |
|---|---|---|---|---|
| 1 | {{ $json.data.emails[0].result }} |
equals | deliverable |
Output 0 |
| 2 | {{ $json.data.emails[0].result }} |
equals | risky |
Output 1 |
| 3 | {{ $json.data.emails[0].result }} |
equals | undeliverable |
Output 2 |
| 4 | {{ $json.data.emails[0].result }} |
equals | unknown |
Output 3 |
São exatamente esses quatro valores que o campo result assume — não existe um quinto. Ainda assim, ative a saída Fallback Output para pegar qualquer coisa fora do esperado e mandar para revisão em vez de descartar em silêncio.
Validando vários emails de uma vez? Insira um node Split Out no campo
data.emailsantes do Switch. Cada email vira um item independente e as expressões ficam mais curtas:{{ $json.result }}em vez de{{ $json.data.emails[0].result }}.
Passo 8: Roteamento por branch
Output 0 — Deliverable: enviar para o CRM
Conecte a saída 0 do Switch a um node HTTP Request (ou ao node nativo do seu CRM). Para HubSpot, por exemplo, use o node HubSpot com a ação Create/Update Contact.
Passe os campos do webhook original junto com os metadados da validação:
email:{{ $('Webhook').first().json.body.email }}firstname:{{ $('Webhook').first().json.body.name }}lead_source:{{ $('Webhook').first().json.body.source }}email_validation_result:{{ $json.data.emails[0].result }}email_validation_score:{{ $json.data.emails[0].score }}
Gravar result e score como propriedades customizadas no CRM permite filtrar leads por qualidade depois, sem revalidar (e sem gastar crédito de novo).
Output 1 — Risky: fila de revisão manual
Conecte a saída 1 a um node Slack (ou Teams, ou qualquer canal de notificação). Configure uma mensagem no canal #ops-lead-review:
Lead para revisão manual:
Email: {{ $('Webhook').first().json.body.email }}
Motivo: {{ $json.data.emails[0].reason }}
Score: {{ $json.data.emails[0].score }}
MX ok: {{ $json.data.emails[0].isv_mx }}
Sem catch-all: {{ $json.data.emails[0].isv_nocatchall }}
Sonda não bloqueada: {{ $json.data.emails[0].isv_noblock }}
Provedor gratuito: {{ $json.data.emails[0].is_free }}
Fonte: {{ $('Webhook').first().json.body.source }}
Os campos isv_* são booleanos e o true é sempre a leitura boa: isv_mx: true significa que o domínio tem MX, isv_nocatchall: true que o domínio não é catch-all, isv_noblock: true que a sonda SMTP não foi bloqueada. Um risky com isv_noblock: false costuma ser um provedor que bloqueia verificação (bol, terra, uol e afins), não um endereço ruim — vale o contato humano antes de descartar. É por isso que esse caso vira risky e não undeliverable.
Output 2 e 3 — Undeliverable e unknown: supressão
undeliverable é endereço que não existe: mande para uma lista de supressão via HTTP Request para a sua própria API, ou grave o lead com um flag email_bloqueado: true. Nunca envie campanha para esse contato.
unknown é o caso em que o servidor do destinatário não deu resposta conclusiva (greylist, timeout). Não é a mesma coisa que inválido — trate como quarentena e revalide o endereço num lote futuro em vez de queimar o contato.
Passo 9: Error handling
O n8n tem tratamento de erro nativo. Ative o Error Workflow nas configurações do workflow para capturar falhas sistêmicas (timeout da API, rede, etc).
Configure retry apenas no node "Consultar lote":
- Retry On Fail: ativado
- Max Tries: 3
- Wait Between Tries: 5000ms
E não ative retry no node "Enviar lote". O POST /api/v1/batch não é idempotente: repetir a chamada cria um lote novo e consome créditos de novo. Se o envio falhar, é melhor deixar a execução quebrar e cair no Error Workflow do que duplicar a cobrança em silêncio.
Os erros que você vai encontrar na prática:
| Status | O que aconteceu | O que fazer |
|---|---|---|
400 |
Payload inválido — emails ausente, vazio ou acima de 100.000 |
Corrigir o body. Não adianta repetir |
401 / 403 |
Chave errada ou conta sem API | O valor do header precisa ser exatamente Bearer ec_live_..., com o espaço. A API é exclusiva do plano Gold |
404 |
Id de lote inexistente ou de outra conta | Conferir o id gravado |
429 |
Estourou 300/min ou 10.000/h | Aumentar o Wait. Os headers x-rate-limit-remaining-minute e x-rate-limit-remaining-hour dizem qual janela estourou |
Todo erro vem com o mesmo corpo, o que facilita o parsing no n8n:
{
"data": null,
"error": "Batch not found"
}
Padrões avançados
Higienizar uma lista inteira
Se o caso de uso for validar uma base inteira (importação de CSV, sincronização de base legada), troque o Webhook trigger por um Schedule trigger e carregue o arquivo com Read Binary File ou Spreadsheet File (ou puxe as linhas do Google Sheets).
O ponto que muda tudo: junte todos os emails num array só e mande um lote único. Um node Code ou Aggregate resolve:
// node Code, modo "Run Once for All Items"
return [{ json: { emails: $input.all().map((i) => i.json.email) } }];
E no node "Enviar lote", o body vira:
{
"emails": {{ JSON.stringify($json.emails) }},
"name": "higiene-{{ $now.toISODate() }}"
}
O anti-padrão aqui é usar Split In Batches de 50 itens e disparar uma chamada por lote pequeno: isso multiplica requisições sem necessidade e é o jeito mais fácil de tomar 429. O endpoint aceita 100.000 emails de uma vez justamente para você não precisar fatiar.
O fluxo fica:
Schedule → Read File → Code (agregar) → Enviar lote → Wait → Consultar lote → IF → Split Out → Switch → [destinos]
Para exportar, adicione um node Spreadsheet File no fim de cada branch e gere um CSV segmentado por result, ou escreva de volta na planilha compartilhada com o time gravando result, score e reason em cada linha.
O estado pending é seu
Vale deixar explícito, porque é a diferença entre uma integração que funciona e uma que trava em produção: o EmailChecker não avisa quando o lote termina. Não existe webhook de conclusão, callback ou push. O GET /api/v1/batch/{id} é o único jeito de saber, e é por isso que o loop Wait + IF existe.
A consequência prática do lado do seu sistema é uma máquina de estados simples:
- O lead entra e é gravado com
email_status: pending. - O formulário recebe
200na hora e o usuário segue a vida. - O workflow do n8n envia o lote, guarda o
ide faz polling. - Quando o lote fecha, o n8n chama a sua API (ou o CRM) e atualiza o registro para
deliverable,risky,undeliverableouunknown.
Enquanto o registro estiver em pending, seu sistema precisa saber o que fazer — normalmente: não disparar campanha, não passar para SDR, mas também não bloquear o cadastro. É uma linha de if na sua aplicação, e é o preço de não travar o formulário do usuário esperando uma sonda SMTP.
Para listas grandes, considere não manter a execução do n8n viva durante todo o processamento: um workflow envia o lote e grava o id, e um segundo workflow com Schedule trigger de hora em hora consulta os ids pendentes. Menos execução pendurada, mesmo resultado.
Conclusão
Com seis nodes de fluxo — Webhook, Enviar lote, Wait, Consultar lote, IF e Switch — mais os nodes de destino, você tem um pipeline de validação de email rodando em produção que:
- Bloqueia emails inválidos antes de chegarem ao CRM
- Roteia leads duvidosos para revisão humana em vez de descartar cegamente
- Responde ao formulário na hora e resolve a validação em segundo plano
- Registra
result,scoreereasonno CRM para segmentação futura
O custo de manter emails ruins no pipeline — bounce rate, reputação de domínio, tempo de SDR — é muito maior do que o custo de validar na entrada. Com n8n e EmailChecker, essa validação vira uma camada invisível que simplesmente funciona.
Próximos passos: a página da integração nativa com n8n tem o payload completo e os erros mais comuns dessa montagem, e o guia de validação de email via API cobre a referência dos campos de resposta, rate limits e o consumo de créditos.