EmailChecker vs mails.so — comparativo técnico
Comparativo técnico honesto entre EmailChecker e mails.so: endpoints, auth, response shape, rate limits, webhooks e um diff real de migração para devs que querem decidir com código.
EmailChecker vs mails.so — comparativo técnico
Antes de entrar no código: esse artigo não foi escrito pra te convencer a migrar. Foi escrito porque essa é a pergunta que aparece com mais frequência no nosso suporte dev — "já uso mails.so, vale a pena trocar?" — e a resposta honesta é "depende". Vou mostrar exatamente do que depende.
Se quiser a versão long-form com benchmarks de cobertura e análise de planos, tem em comparativo mails.so vs EmailChecker com dados de cobertura. Aqui o foco é: código, diff de migração, e quando não migrar.
Contexto: por que comparar esses dois
mails.so e EmailChecker resolvem o mesmo problema na superfície — validar se um endereço de email existe e aceita mensagens antes de você enviar. A diferença começa quando você vai além do check básico de formato.
mails.so é um serviço europeu (preços em EUR), com planos bem definidos e uma proposta de "no-code first" — Zapier e Make são cidadãos de primeira classe na documentação deles. A API existe e funciona, mas a experiência de dev não é o foco central do produto.
EmailChecker nasceu API-first. A decisão arquitetural mais visível disso é que toda a lógica de validação que o dashboard exibe é a mesma que você acessa via API — sem endpoints "lite" para integrações e endpoints "completos" só no portal.
Com isso dito, vamos ao que interessa.
Endpoints: mapeamento direto
| Operação | mails.so | EmailChecker |
|---|---|---|
| Validar email (single) | GET /v1/validate?email= |
GET /v1/verify?email= |
| Validar email (single) | POST /v1/validate |
POST /v1/verify |
| Validação em lote | POST /v1/validate/bulk |
POST /v1/bulk |
| Status de job em lote | GET /v1/bulk/{job_id} |
GET /v1/bulk/{job_id}/status |
| Download resultado bulk | GET /v1/bulk/{job_id}/result |
GET /v1/bulk/{job_id}/results |
| Saldo de créditos | GET /v1/account |
GET /v1/account/credits |
| Webhooks (config) | painel web | POST /v1/webhooks |
O mapeamento é quase 1:1. A diferença que vai te custar tempo não é o path — é o shape da resposta, que cobre a seção seguinte.
Documentação completa de todos os endpoints na referência da API REST. A seção 8 do guia de validação de email via API cobre especificamente o drop-in replacement.
Auth: troca de header, mesma ideia
mails.so usa um header proprietário:
x-mails-api-key: mso_sua_chave_aqui
EmailChecker usa o padrão Bearer do RFC 6750:
Authorization: Bearer ec_sua_chave_aqui
Na prática, é uma linha de diff no seu cliente HTTP. Se você usa uma constante API_KEY_HEADER, é literalmente uma mudança de string. O prefixo da chave também muda (mso_ para ec_) — guarde isso pro momento de rotacionar as credenciais, porque copiar e colar a chave antiga vai gerar 401 silencioso.
Response shape: campo a campo
Aqui mora a maior parte do trabalho de migração. Os campos têm nomes diferentes, e alguns conceitos não têm equivalente direto.
| Conceito | mails.so | EmailChecker | Observação |
|---|---|---|---|
| Status geral | result |
status |
Valores diferentes — ver abaixo |
| Email normalizado | email |
email |
Igual |
| Domínio | domain |
domain |
Igual |
| MX existe | mx (bool) |
mx_found (bool) |
Renomeado |
| SMTP respondeu | smtp (bool) |
smtp_valid (bool) |
Renomeado |
| Descartável | disposable (bool) |
disposable (bool) |
Igual |
| Role account | role (bool) |
role_account (bool) |
Renomeado |
| Score de risco | — | score (0–100) |
Só no EC |
| Sugestão de correção | did_you_mean |
suggestion |
Renomeado |
| Catch-all | — | catch_all (bool) |
Só no EC |
| Tempo de resposta | — | latency_ms |
Só no EC |
| Mensagem de erro | error |
error.code + error.message |
EC estrutura o erro |
Valores do campo de status
mails.so retorna result com: "valid", "invalid", "risky", "unknown"
EmailChecker retorna status com: "valid", "invalid", "catch_all", "disposable", "unknown"
Perceba que "risky" do mails.so é um conceito composto — pode significar catch-all, role account ou score alto de risco. No EC, esses casos são campos separados, então sua lógica de decisão fica mais granular. Isso é bom, mas exige que você reescreva os ifs que dependem de result === "risky".
Rate limits: números concretos
mails.so publica os seguintes limites por plano:
- Pro (10k validações/mês): 10 req/s
- Business (50k/mês): 30 req/s
- Unlimited: 60 req/s
EmailChecker opera com os seguintes limites:
- Starter (5k/mês): 5 req/s
- Growth (25k/mês): 20 req/s
- Scale (100k/mês): 50 req/s
- Pro (500k/mês): 100 req/s
Nos dois serviços, o header X-RateLimit-Remaining está presente na resposta. No EC, Retry-After é enviado automaticamente no 429 — você pode implementar um retry com backoff exponencial sem precisar adivinhar o cooldown.
Se o seu caso de uso é bulk assíncrono, rate limit de single-endpoint importa menos — os dois serviços processam jobs em lote sem contar cada validação individual contra o rate limit da API síncrona.
Webhooks: anatomia de um evento
mails.so não expõe configuração de webhook via API — você configura no painel e não tem controle programático. O payload que chega é assim:
{
"event": "bulk.completed",
"job_id": "mso_job_abc123",
"completed_at": "2026-06-12T14:32:00Z",
"summary": {
"total": 1000,
"valid": 847,
"invalid": 103,
"risky": 50
}
}
No EmailChecker, você registra webhooks via API e recebe um secret para validar a assinatura HMAC-SHA256:
{
"event": "bulk.completed",
"job_id": "ec_job_xyz789",
"timestamp": "2026-06-12T14:32:00Z",
"signature": "sha256=a1b2c3...",
"data": {
"total": 1000,
"valid": 851,
"invalid": 99,
"catch_all": 28,
"disposable": 12,
"unknown": 10
}
}
A diferença prática: você consegue verificar a autenticidade do webhook no EC. Com mails.so, qualquer POST no endpoint vai ser processado — você precisa implementar algum token secreto na URL ou aceitar o risco. Se você vai consumir esses eventos em produção, vale ler antes os padrões de arquitetura para webhooks de validação — HMAC, idempotência e dead letter queue.
Diff de migração: código real
Caso 1: validação single, Node.js
- const API_URL = 'https://api.mails.so/v1/validate';
+ const API_URL = 'https://api.emailchecker.com.br/v1/verify';
async function validateEmail(email) {
const res = await fetch(`${API_URL}?email=${encodeURIComponent(email)}`, {
headers: {
- 'x-mails-api-key': process.env.MAILS_API_KEY,
+ 'Authorization': `Bearer ${process.env.EC_API_KEY}`,
}
});
const data = await res.json();
- return data.result === 'valid';
+ return data.status === 'valid';
}
Caso 2: response parsing com mais campos
function parseValidationResult(data) {
return {
- isValid: data.result === 'valid',
- isRisky: data.result === 'risky',
- isDisposable: data.disposable,
- hasMX: data.mx,
- suggestion: data.did_you_mean,
+ isValid: data.status === 'valid',
+ isRisky: data.score > 60, // granular agora
+ isCatchAll: data.catch_all, // campo novo
+ isDisposable: data.disposable,
+ isRoleAccount: data.role_account, // renomeado
+ hasMX: data.mx_found, // renomeado
+ suggestion: data.suggestion, // renomeado
};
}
Caso 3: webhook handler com validação de assinatura (EC only)
// Novo código necessário no EC — não existe equivalente no mails.so
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
app.post('/webhooks/emailchecker', (req, res) => {
const sig = req.headers['x-emailchecker-signature'];
if (!verifyWebhookSignature(req.body, sig, process.env.EC_WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// processa req.body.data...
res.sendStatus(200);
});
Caso 4: bulk com polling (Python)
import requests
- BASE = 'https://api.mails.so/v1'
- HEADERS = {'x-mails-api-key': os.environ['MAILS_API_KEY']}
+ BASE = 'https://api.emailchecker.com.br/v1'
+ HEADERS = {'Authorization': f"Bearer {os.environ['EC_API_KEY']}"}
def submit_bulk(emails: list[str]) -> str:
- r = requests.post(f'{BASE}/validate/bulk', headers=HEADERS, json={'emails': emails})
+ r = requests.post(f'{BASE}/bulk', headers=HEADERS, json={'emails': emails})
return r.json()['job_id']
def get_results(job_id: str) -> list:
- r = requests.get(f'{BASE}/bulk/{job_id}/result', headers=HEADERS)
+ r = requests.get(f'{BASE}/bulk/{job_id}/results', headers=HEADERS)
return r.json()['results']
Latência típica
Em testes de produção a partir de servidores no Brasil (São Paulo), os números medianos que observamos:
- mails.so: 280–420 ms para single validation (servidores na Europa)
- EmailChecker: 80–160 ms para single validation (infraestrutura no Brasil)
Essa diferença tem origem geográfica — mails.so roda na Europa, e a latência transatlântica é o fator principal. Se o seu backend roda em sa-east-1 ou num VPS local, a diferença vai aparecer em produção.
Para bulk, latência de endpoint importa menos — o que importa é throughput de processamento do job, que depende mais de fila interna do que de RTT de rede.
Custo: comparação de planos equivalentes
| Volume mensal | mails.so | EmailChecker |
|---|---|---|
| 10k validações | EUR 4,80/mês (~R$ 29) | R$ 39/mês |
| 50k validações | EUR 9,80/mês (~R$ 59) | R$ 89/mês |
| 100k validações | EUR 9,80/mês (~R$ 59) | R$ 149/mês |
| 500k validações | EUR 49,80/mês (~R$ 300) | R$ 349/mês |
| Ilimitado | EUR 49,80/mês (~R$ 300) | — |
mails.so é mais barato em volume absoluto, especialmente nos planos de 50k e 100k. Isso é verdade e merece ser dito. Se você está num plano Business do mails.so e o custo é o driver principal, a troca não se justifica só por preço.
Quando NAO migrar
Honestamente: se você está satisfeito com mails.so, não migre.
Casos específicos onde faz sentido ficar:
Volume baixo com custo como prioridade. Os planos do mails.so são mais baratos em EUR, e se você está num plano de 10k ou 50k validações com orçamento limitado, a diferença de preço supera as vantagens técnicas.
Integração no-code já funcionando. Se você usa mails.so via Zapier ou Make e não tem um backend próprio, a migração não faz sentido — EmailChecker é API-first e o esforço de reconfiguração não vai trazer retorno.
Backend fora do Brasil. Se sua infra roda em us-east-1 ou eu-west-1, a vantagem de latência do EC some — os dois vão ter RTT parecido do ponto de vista do seu servidor.
Sem necessidade de webhooks verificados ou catch-all granular. Se result === 'valid' resolve teu problema e você nunca precisou de mais granularidade, as funcionalidades extras do EC são overhead que você vai ignorar de qualquer forma.
Migrar tem custo real: QA dos campos renomeados, atualização de dashboards internos que usam o shape antigo, rotação de chaves, teste do webhook handler novo. Isso é horas de trabalho de eng, e precisa valer a pena.
Conclusão
A migração de mails.so para EmailChecker é tecnicamente simples — um dia de trabalho para um dev familiarizado com as duas APIs. Os pontos que vão te custar tempo são o remapeamento de campos (especialmente result -> status com semântica diferente) e a lógica de webhook se você usa bulk.
Os motivos que fazem sentido migrar:
- Latência é importante e você roda no Brasil
- Você precisa de catch-all detection ou score granular de risco
- Webhooks verificados via HMAC são um requisito de segurança
- Você quer configurar webhooks programaticamente, não via painel
Os motivos que não fazem sentido migrar:
- Custo é o fator principal e você está num plano médio do mails.so
- Você usa Zapier/Make e não tem backend
- Tudo que você precisa é
validvsinvalid
Se ainda tiver dúvida, a documentação completa da API e a seção 8 do guia de validação de email via API têm exemplos de drop-in para os casos de uso mais comuns. Também tem o comparativo long-form mails.so vs EmailChecker se você quiser os dados de cobertura e benchmark de SMTP por país.
Se a sua dúvida é mais ampla — qual validador escolher no geral, não só entre esses dois — veja o review de 10 ferramentas de validação de email. E se a comparação for entre os dois validadores brasileiros, o comparativo entre EmailChecker e SafetyMails cobre esse caso.
João Costa — Engenharia, EmailChecker