Mailcheer
Envie e-mails transacionais e campanhas de newsletter a partir de um agente: assinantes, segmentos, lista de supressão, envio e estatísticas — com uma única chave de API. Funciona na Amazon SES, hospedado na Europa.
Servidor MCP hospedado
npx add-mcp 'https://mailcheer.com/api/mcp'Instala no Claude Code, Codex, Cursor e outros
Documentação
API de Email e servidor MCP
A API REST do Mailcheer e o servidor MCP: envie emails transacionais, gerencie assinantes, crie e envie campanhas, tudo a partir da sua aplicação ou agente de IA.
O Mailcheer é controlável externamente: a partir da sua aplicação, de um script, ou de um agente como Claude Code, ChatGPT ou Codex. Dois pontos de entrada, uma chave.
| Para quem | Endereço | |
|---|---|---|
| API REST | Código — qualquer linguagem que possa fazer uma requisição HTTP. | https://mailcheer.com/api/v1 |
| Servidor MCP | Agentes de IA, que descobrem as ferramentas disponíveis por conta própria. | https://mailcheer.com/api/mcp |
Sua primeira chave
No seu workspace Mailcheer: Conta → API e agentes de IA → Nova chave. Dê um nome a ela e marque o que ela pode fazer.
A chave completa é mostrada apenas uma vez. Guardamos somente uma impressão digital: se você a perder, ninguém pode recuperá-la para você — crie uma nova e revogue a antiga. Este é o preço de garantir que uma cópia roubada do nosso banco de dados não produza nenhuma chave utilizável, e é o preço certo.
Guarde-a como uma senha: nas variáveis de ambiente do seu serviço, nunca em código compartilhado ou em uma página pública.
Permissões da chave
| Permissão | O que ela libera |
|---|---|
emails:send | Enviar emails transacionais e ler seu status. |
subscribers:read | Ler assinantes e a lista de supressão. |
subscribers:write | Adicionar, atualizar e cancelar a assinatura de assinantes. |
campaigns:read | Ler campanhas e suas estatísticas. |
campaigns:write | Criar e enviar campanhas, excluir um rascunho. |
webhooks:read | Ler assinaturas de eventos e seu log. |
webhooks:write | Criar, editar e excluir assinaturas de eventos. |
Marque apenas o que você precisa. Uma chamada fora do escopo da chave retorna 403, e nada a contorna — é a única proteção que se sustenta contra um agente autônomo: você não conta com a cautela dele, você remove o botão.
As permissões são escolhidas na criação e nunca mudam. Uma chave cujo escopo pode ser expandido depois não significa nada: a pessoa que a recebeu acredita que tem acesso somente leitura e acaba com direitos de envio, sem ser informada.
Enviar um email
O ponto de entrada mais comum: a fatura, o alerta, a redefinição de senha — tudo o que sua aplicação escreve para uma pessoa por vez.
curl -X POST https://mailcheer.com/api/v1/emails \
-H "Authorization: Bearer mch_live_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: invoice-2026-0412" \
-d '{
"from": "Your brand <hello@yourdomain.com>",
"to": "customer@example.com",
"subject": "Your September invoice",
"html": "<p>Here it is.</p>"
}'
A resposta volta como 202:
{
"id": "cmu651xf200021n7nm68sikfw",
"object": "email",
"from": "hello@yourdomain.com",
"to": ["customer@example.com"],
"subject": "Your September invoice",
"created_at": "2026-09-18T07:12:44.102Z"
}
202, não 200: nosso provedor de envio aceitou a mensagem; ela ainda não está em uma caixa de entrada. A entrega é confirmada alguns segundos depois:
curl https://mailcheer.com/api/v1/emails/cmu651xf200021n7nm68sikfw \
-H "Authorization: Bearer mch_live_…"
O campo status muda de sent para delivered, ou para bounced se o endereço não existir, ou para complained se a pessoa marcou a mensagem como spam. Em ambos os últimos casos, o endereço é adicionado automaticamente à lista de supressão — sua aplicação não precisa lidar com isso.
Cancelamento de assinatura com um clique
Se você escreve para pessoas que não escreveram para você primeiro — uma newsletter, um alerta ao qual alguém se inscreveu, uma página de status — Gmail e Yahoo esperam um link de cancelamento de assinatura nos cabeçalhos da mensagem, não apenas no rodapé da página. Eles exigem isso desde fevereiro de 2024.
Passe o endereço em unsubscribe_url, e o Mailcheer define ambos os cabeçalhos, que sempre andam juntos:
curl -X POST https://mailcheer.com/api/v1/emails \
-H "Authorization: Bearer mch_live_…" \
-H "Content-Type: application/json" \
-d '{
"from": "Your brand <alerts@yourdomain.com>",
"to": "customer@example.com",
"subject": "Your monthly report",
"html": "<p>Here it is.</p>",
"unsubscribe_url": "https://yourdomain.com/unsubscribe/abc123"
}'
Seu endpoint deve aceitar um POST e cancelar a assinatura sem pedir confirmação — é isso que significa um clique. Um GET no mesmo endereço pode levar a uma página legível, para clientes de email que fazem um ou outro.
Sem este cabeçalho, a única saída que você oferece é o botão de Spam — e é a reputação do seu domínio de envio que paga por isso, não a da mensagem.
⚠️ List-Unsubscribe ainda é rejeitado dentro de headers: o Mailcheer o escreve, você apenas fornece o destino. Isso garante que List-Unsubscribe-Post sempre venha junto — sem esse segundo cabeçalho, o Gmail não mostra botão.
Anexos
Uma cotação, uma fatura, um folheto: passe-os em attachments, no formato da Resend — filename e content, o arquivo codificado em base64.
curl -X POST https://mailcheer.com/api/v1/emails \
-H "Authorization: Bearer mch_live_…" \
-H "Content-Type: application/json" \
-d '{
"from": "Your brand <hello@yourdomain.com>",
"to": "client@example.com",
"subject": "Your quote",
"text": "The quote is attached.",
"attachments": [
{
"filename": "quote-2026-09.pdf",
"content": "JVBERi0xLjQKJcfsj6IK…",
"content_type": "application/pdf"
}
]
}'
Em Node, o conteúdo leva uma linha:
import { readFileSync } from "node:fs";
const quote = {
filename: "quote-2026-09.pdf",
content: readFileSync("./quote-2026-09.pdf").toString("base64"),
};
content_type é opcional: é inferido da extensão (.pdf → application/pdf), com fallback para application/octet-stream. No máximo vinte anexos por mensagem.
O limite é do nosso provedor de envio: 40 MB por mensagem uma vez codificada, o que equivale a aproximadamente 30 MB de arquivos reais — o base64 adiciona um terço. Além disso, a chamada é rejeitada com um 422, nomeando o arquivo, seu tamanho e o tamanho atingido. Isso é deliberado: uma recusa que explica é melhor do que uma mensagem que sai sem seu anexo.
Rejeitados da mesma forma, e sempre em voz alta:
- extensões que os provedores de caixa de entrada rejeitam —
.exe,.bat,.js,.vbs,.scr… Coloque o arquivo em um.zip, ou envie um link de download; - um
contentque não seja base64 válido; - um campo
pathapontando para uma URL a ser buscada: nosso servidor não segue um endereço que você escolhe. Codifique o arquivo.
Quando uma mensagem carrega um anexo, ela sai como uma mensagem MIME completa em vez de uma simples. Todo o resto permanece inalterado: cc, bcc, reply_to, seus cabeçalhos, cancelamento de assinatura com um clique e suas tags se comportam de forma idêntica — e bcc ainda não aparece em nenhum cabeçalho da mensagem recebida.
Cópias
cc e bcc aceitam um endereço ou um array de 1 a 50, assim como to. Ambos contam para sua cota e passam pelas mesmas verificações — um endereço suprimido rejeita a chamada inteira, seja ele um destinatário ou uma cópia.
Rastreamento de abertura
Um email HTML carrega uma imagem invisível de um pixel que conta aberturas. Sem track_opens na requisição, a configuração Rastrear aberturas do seu workspace decide (tela Workspace do aplicativo); track_opens: true ou false a substitui para aquele email. Um email em texto puro não carrega pixel. GET /api/v1/emails/{id} retorna track_opens: quando é false, opened_at permanece null porque as aberturas não são rastreadas, não porque ninguém abriu.
Na França, a recomendação da CNIL de 14 de abril de 2026 torna a medição de aberturas com um pixel, para rastrear o desempenho de campanhas, sujeita ao consentimento prévio do destinatário. Um código de login ou uma redefinição de senha não tem motivo para ser rastreado: envie track_opens: false.
As cinco regras de todo envio
Elas não podem ser contornadas, e são as mesmas de uma campanha enviada pela interface.
1. from deve estar em um domínio verificado no seu workspace. Caso contrário, 422 unverified_from_domain, com uma lista dos seus domínios verificados na mensagem. GET /api/v1/me também os retorna.
2. Um endereço na lista de supressão é recusado, com seu motivo — cancelamento de assinatura, endereço morto, reclamação. A chamada inteira falha, incluindo outros destinatários: um envio parcial do qual você não sabe é o pior resultado possível, porque você pensaria que notificou todos.
3. A cota mensal do seu plano conta esses envios da mesma forma que campanhas — e o que já está aguardando na fila. É a mesma contagem de envios, a mesma fatura. Um envio que não cabe retorna 402 quota_exceeded (veja abaixo). No plano gratuito, os emails gratuitos de um domínio de envio atendem um workspace por mês: em outros lugares, é 402 free_plan_domain_used.
4. Uma taxa de rejeição ou reclamação muito alta suspende o envio. Os limites são os da Amazon: 5% de rejeições, 0,1% de reclamações. Uma aplicação que escreve para endereços inventados causa o mesmo dano que uma campanha em uma lista comprada.
5. Nada contorna o double opt-in. Um assinante adicionado via API recebe uma confirmação, a menos que double_opt_in: false seja explícito — e então você assume a responsabilidade pelo consentimento. O mesmo endereço recebe no máximo uma confirmação a cada 2 minutos e 3 por 24 horas, todos os canais combinados (formulário, API, MCP): uma nova chamada atualiza o registro sem enviar outro email, e a resposta diz o porquê (confirmation_sent: false, confirmation_not_sent_reason, confirmation_retry_at). Além disso, cada lembrete é uma reclamação potencial contra seu domínio.
Onde seu workspace está
GET /api/v1/me é a primeira chamada a fazer, e o reflexo certo antes de um envio importante: ela diz a quem a chave pertence, de quais endereços escrever — e se o envio vai caber. Nenhuma permissão específica é necessária.
curl https://mailcheer.com/api/v1/me -H "Authorization: Bearer mch_live_…"
{
"object": "account",
"organization": { "id": "org_3f9", "name": "Your brand", "slug": "your-brand" },
"key": { "name": "Production", "scopes": ["emails:send", "subscribers:read"] },
"plan": { "id": "free", "name": "Découverte", "emails_per_month": 3000 },
"usage": {
"period": "2026-09",
"emails_sent": 2410,
"emails_in_flight": 120,
"emails_remaining": 470,
"resets_at": "2026-10-01T00:00:00.000Z"
},
"billing": {
"status": "none",
"subscribed_plan": null,
"current_period_end": null,
"cancel_at_period_end": false,
"scheduled_change": null,
"manage_url": "https://mailcheer.com/reglages/facturation"
},
"limits": {
"members": { "used": 1, "pending_invitations": 0, "max": 1 },
"sending_domains": { "used": 1, "max": 1 },
"daily": null
},
"subscribers": 551,
"sending_domains": [{ "domain": "yourdomain.com", "verified": true }],
"senders": [{ "id": "snd_71a", "from": "hello@yourdomain.com", "name": "Your brand", "default": true }]
}
usage—emails_sent: o que saiu neste mês, todos os canais juntos.emails_in_flight: o que está aguardando na fila (uma campanha em andamento, envios de automação reservados) — já prometido.emails_remaining: o que ainda pode ser enviado, fila deduzida, nunca negativo (nullem um plano ilimitado).resets_at: quando o contador reinicia, no dia 1º do próximo mês às 00:00 UTC.billing—statusénonesem uma assinatura paga; caso contrário, o status do pagamento:active,past_due(uma cobrança falhou, o plano permanece ativo enquanto tenta novamente),unpaid(as tentativas foram abandonadas: o workspace opera nos limites do Discovery),canceled…subscribed_plannomeia o plano sendo cobrado — ele pode diferir deplan.idapós um pagamento falho.scheduled_changeanuncia um downgrade ou cancelamento agendado ({ "plan": "free", "effective_at": "…" }).manage_urlé a tela onde o proprietário ou um administrador muda o plano.limits— membros (um convite pendente ocupa um assento), domínios de envio, edaily: o limite de um novo workspace em um período contínuo de 24 horas (100 emails nos primeiros três dias, 500 até o sétimo),nullquando não se aplica.
Software conectado ao Mailcheer — um CRM que envia para seus usuários, por exemplo — pode então mostrar "470 emails restantes até 1º de outubro" em vez de descobrir a recusa. O proprietário do workspace e os administradores recebem um email em 50%, 80% e 95% da cota, uma vez por mês cada — sem email em 100%: a recusa diz isso.
A cota em cada resposta
Não é necessário chamar GET /api/v1/me antes de cada envio: toda resposta autenticada da API — sucesso ou erro, 402 incluído — e do servidor MCP carrega o estado da cota deste mês.
| Cabeçalho | Valor |
|---|---|
Mailcheer-Quota-Limit | Emails por mês no seu plano, ou unlimited. |
Mailcheer-Quota-Used | Enviados neste mês mais o que está aguardando na fila (emails_sent + emails_in_flight). |
Mailcheer-Quota-Remaining | O que ainda pode ser enviado, fila deduzida, nunca negativo — ou unlimited. |
Mailcheer-Quota-Reset | Quando o contador reinicia, em ISO 8601: o dia 1º do próximo mês, 00:00 UTC. |
curl -i https://mailcheer.com/api/v1/emails -H "Authorization: Bearer mch_live_…" …
# HTTP/1.1 202 Accepted
# Mailcheer-Quota-Limit: 3000
# Mailcheer-Quota-Used: 2531
# Mailcheer-Quota-Remaining: 469
# Mailcheer-Quota-Reset: 2026-10-01T00:00:00.000Z
A resposta a um envio aceito já conta esse envio. Uma resposta sem uma chave válida (401) não carrega nenhum: ela não conhece seu workspace. Se a cota não puder ser lida naquele momento, a resposta ainda sai, sem esses cabeçalhos.
Recebendo notificações: o webhook quota.threshold_reached
Assine um endereço ao evento quota.threshold_reached (POST /api/v1/webhooks, ou Configurações → API): o Mailcheer o chama quando a cota deste mês atinge 50, 80, 95 e 100%, uma vez por mês por limite, no momento em que o email que cruza o limite é aceito. Se um único envio cruzar vários, apenas o mais alto é enviado. O corpo é assinado e repetido como todo evento:
{
"id": "evt_3kT9xQ2mV7aB1cD4",
"type": "quota.threshold_reached",
"created_at": "2026-09-24T16:02:11.000Z",
"data": {
"threshold": 80,
"plan": "free",
"quota": 3000,
"sent": 2400,
"in_flight": 35,
"remaining": 565,
"resets_at": "2026-10-01T00:00:00.000Z",
"period": "2026-09"
}
}
threshold é o limite atingido, em porcentagem; os outros campos significam o que significam no details de uma recusa 402. Em 100%, apenas o webhook dispara (sem email): a partir daí, os envios retornam 402 quota_exceeded até resets_at.
Quando a cota não cobre um envio
Um envio que não cabe no que resta deste mês é recusado por inteiro, antes que qualquer coisa saia: nada é enviado, nada é enfileirado. A resposta é um 402 com código quota_exceeded, em POST /api/v1/emails como em POST /api/v1/campaigns/CAMP_ID/send, e a ferramenta MCP fazendo o mesmo movimento retorna o mesmo erro:
{
"error": {
"code": "quota_exceeded",
"message": "…",
"details": {
"plan": "free",
"quota": 3000,
"sent": 2940,
"in_flight": 20,
"remaining": 40,
"requested": 250,
"resets_at": "2026-10-01T00:00:00.000Z"
}
},
"statusCode": 402,
"message": "…",
"name": "quota_exceeded"
}
remaining é quota − sent − in_flight; requested é o que a chamada pediu (destinatários, incluindo cópias). Dois caminhos possíveis: mudar o plano (billing.manage_url) ou aguardar o resets_at. Tentar repetir a mesma chamada antes de qualquer um dos dois resultará na mesma recusa.
O plano gratuito: um domínio, um workspace por mês
No plano gratuito, os 3.000 e-mails do mês estão vinculados ao domínio de envio, não à conta. Um domínio registrado (acme.com, incluindo subdomínios) atende ao plano gratuito para um único workspace por mês UTC: o primeiro que enviar com ele. Outro workspace gratuito que enviar a partir desse domínio, ou de um de seus subdomínios, no mesmo mês receberá um 402 diferente, também recusado por completo:
{
"error": {
"code": "free_plan_domain_used",
"message": "The free plan is per sending domain, not per account: this domain (news.acme.com, part of acme.com) has already used it this month in another workspace. Upgrade to a paid plan to send from several workspaces, or wait until October 1 at 00:00 UTC.",
"details": {
"domain": "news.acme.com",
"root_domain": "acme.com",
"period": "2026-09",
"resets_at": "2026-10-01T00:00:00.000Z"
}
},
"statusCode": 402,
"message": "…",
"name": "free_plan_domain_used"
}
Diferencie os dois 402 pelo error.code. Fazer upgrade para um plano pago remove essa limitação imediatamente; os planos pagos não são afetados.
Nunca envie duas vezes
Uma biblioteca HTTP que não recebeu nossa resposta repetirá a chamada. Esse é o papel dela e, sem uma precaução, seu cliente receberá a mesma fatura duas vezes.
Adicione o cabeçalho Idempotency-Key com um valor único por envio — o número da fatura, o ID do pedido, um UUID:
Idempotency-Key: invoice-2026-0412
Repetir a mesma chamada retorna a mesma resposta, com o mesmo id, sem um segundo envio. O cabeçalho Idempotent-Replay: true informa que foi uma repetição. A mesma chave com um corpo diferente retorna 409: isso não é uma nova tentativa, é um erro do seu lado, e retornar a resposta do outro envio seria pior do que informar isso.
Assinantes
# Add — the person receives a confirmation and enters "pending"
curl -X POST https://mailcheer.com/api/v1/subscribers \
-H "Authorization: Bearer mch_live_…" \
-H "Content-Type: application/json" \
-d '{"email":"marie@example.com","firstName":"Marie","tags":["customers"]}'
# List, page by page
curl "https://mailcheer.com/api/v1/subscribers?limit=50&status=subscribed" \
-H "Authorization: Bearer mch_live_…"
# Unsubscribe
curl -X DELETE https://mailcheer.com/api/v1/subscribers/marie%40example.com \
-H "Authorization: Bearer mch_live_…"
DELETE não apaga o registro: a pessoa passa para unsubscribed e o endereço dela entra na lista de supressão. Excluir o registro permitiria que ela reaparecesse na próxima importação de arquivo — você teria respeitado o verbo HTTP e traído a pessoa.
Um endereço que cancelou a assinatura não pode reassinar via API. Somente a pessoa pode voltar, por meio de um formulário. Um cancelamento que um programa pode desfazer não vale nada.
Paginação
As listas retornam { data, has_more, next_cursor }. Passe next_cursor como ?cursor= para a próxima página.
Sem número de página, por design: em uma lista onde escritas acontecem ao mesmo tempo que leituras — que é exatamente o caso de uma API — page=2 pula linhas e mostra outras duas vezes. Um cursor não se move.
Campanhas
Criar e enviar são duas ações separadas. Isso não é burocracia: é o que permite revisar uma carta antes de ela ir para três mil pessoas.
# 1. The draft — nothing is sent
curl -X POST https://mailcheer.com/api/v1/campaigns \
-H "Authorization: Bearer mch_live_…" \
-H "Content-Type: application/json" \
-d '{
"name": "September newsletter",
"subject": "What we learned this summer",
"text": "# Hello\n\nHere is this month'\''s news."
}'
# 2. Send — irreversible
curl -X POST https://mailcheer.com/api/v1/campaigns/CAMP_ID/send \
-H "Authorization: Bearer mch_live_…"
# 3. Track
curl https://mailcheer.com/api/v1/campaigns/CAMP_ID \
-H "Authorization: Bearer mch_live_…"
Em text, uma linha em branco separa dois parágrafos e # no início de uma linha cria um título. Para layout completo (imagens, botões, divisores), passe content com os blocos do editor.
O envio retorna 202 com queued: os destinatários são bloqueados e as mensagens são então enviadas na taxa que nosso provedor permite. Um envio de cinquenta mil e-mails não cabe em uma única solicitação HTTP, e afirmar o contrário daria a você um "enviado" para um trabalho que está apenas começando.
As taxas de abertura e clique são calculadas sobre mensagens entregues, nunca sobre o número total de destinatários: um endereço morto não deve reduzir a taxa daqueles que realmente receberam.
Aberturas só contam em mensagens que continham o pixel de rastreamento. Quando a configuração Rastrear aberturas do workspace estava desativada durante todo o envio, opens_tracked é false e open_rate e human_open_rate são null — nunca um 0% enganoso.
Cada taxa vem duas vezes: open_rate e click_rate incluem bots, como a maioria das ferramentas conta; human_open_rate e human_click_rate os excluem. Um bot é uma abertura ou um clique dentro de dois minutos após a entrega (os gateways de segurança de caixas de correio empresariais visitam cada link quando a mensagem chega, relays de privacidade pré-carregam imagens), ou um robô que se declara no user agent, ou de uma faixa de endereços que seu operador publica (Google, Bing). Relays de privacidade (Apple Mail, Gmail, Yahoo) não são bots por si só. A regra é deliberadamente estrita: uma pessoa que abre dentro do minuto é contada como bot — uma taxa ligeiramente baixa em vez de uma inflada.
Excluindo um rascunho
curl -X DELETE https://mailcheer.com/api/v1/campaigns/CAMP_ID \
-H "Authorization: Bearer mch_live_…"
Retorna { "object": "campaign", "id": "…", "deleted": true }. Somente um rascunho (draft) pode ser excluído, e isso não pode ser desfeito. Uma campanha agendada, em envio, enviada ou arquivada responde com 409 conflict, com seu status em details.status: o que já saiu, ou sairá, mantém seus envios e suas estatísticas.
Escrevendo para um grupo em vez da lista inteira
Sem um corpo, o envio vai para todo o público da campanha: cada assinante ativo do workspace (ou do segmento escolhido na interface), menos a lista de supressão. Para escrever apenas para parte dele — pessoas que não responderam, seus clientes, quem se inscreveu para um workshop — passe os endereços delas em to:
curl -X POST https://mailcheer.com/api/v1/campaigns/CAMP_ID/send \
-H "Authorization: Bearer mch_live_…" \
-H "Content-Type: application/json" \
-d '{ "to": ["claire@example.com", "marc@example.com", "former@example.com"] }'
to só pode reduzir. A carta vai para os endereços solicitados que também são assinantes ativos do público, e nunca para um endereço na lista de supressão: alguém que cancelou, deu bounce ou reclamou não recebe nada, mesmo que o endereço esteja em to. A resposta informa quem foi mantido, quem foi excluído e por quê:
{
"id": "cmp_8d2", "object": "campaign", "status": "sending", "queued": 2,
"audience": {
"requested": 3, "duplicates": 0, "retained": 2, "excluded": 1,
"reasons": { "invalid": 0, "not_in_list": 0, "pending": 0, "unsubscribed": 1,
"bounced": 0, "complained": 0, "suppressed": 0, "outside_segment": 0 },
"excluded_addresses": [ { "email": "former@example.com", "reason": "unsubscribed" } ]
},
"note": "2 recipient(s) queued. …"
}
A contagem sempre fecha: requested = duplicates + retained + excluded. Maiúsculas/minúsculas e espaços ao redor são ignorados (Claire@Example.com é a mesma pessoa). Os motivos: invalid (não é um endereço), not_in_list (nenhum assinante do workspace o possui), pending (inscrição ainda não confirmada), unsubscribed, bounced, complained, suppressed (assinante, mas na lista de supressão) e outside_segment (fora do segmento que a campanha segmenta).
Três regras que valem a pena conhecer:
- Uma lista vazia não é "sem lista".
"to": []não envia para ninguém: o envio é recusado (422). Para escrever para a lista inteira, não envie nenhum campoto. - Sem
toem uma campanha com teste A/B. A versão vencedora sai horas depois, calculada sobre todo o público da campanha; a lista de endereços não sobreviveria a isso. O envio é recusado em vez de ir para todos. - Campos desconhecidos são ignorados, exceto semelhantes a
dry_runeto.dryRun,dry-run,DRY_RUN,test,simulate,preview… eTo,TO,recipients,emails,to_emails,destinataires,adresses,audience… são recusados (422), nomeando o campo correto: ignorados, os primeiros enviariam a campanha de verdade, os últimos a enviariam para a lista inteira.POST /api/v1/audiencerecusa qualquer campo desconhecido.
Até 50.000 endereços por chamada.
Pré-visualização antes do envio
"dry_run": true executa todas as verificações de um envio real — remetente, conteúdo, reputação, cota, destinatários — e não enfileira nada. A resposta é um 200:
{ "id": "cmp_8d2", "object": "send_preview", "dry_run": true,
"would_send": true, "blocked_reason": null, "recipients": 2,
"audience": { "requested": 3, "retained": 2, "excluded": 1, … } }
Se o envio real fosse recusado, would_send é false e blocked_reason dá a mensagem exata que ele retornaria. Esse é o número para mostrar à pessoa antes de ela confirmar.
Para fazer a mesma pergunta antes de a campanha existir — enquanto alguém está escolhendo para quem escrever, no seu próprio software — POST /api/v1/audience retorna o mesmo detalhamento sem criar nada (escopo subscribers:read):
curl -X POST https://mailcheer.com/api/v1/audience \
-H "Authorization: Bearer mch_live_…" \
-H "Content-Type: application/json" \
-d '{ "to": ["claire@example.com", "marc@example.com"] }'
# → { "object": "audience", "recipients": 2, "audience": { … } }
Sem to, ele retorna quantos assinantes uma campanha enviada para a lista inteira alcançaria. Verificações específicas da campanha (assunto, conteúdo, cota) permanecem as de dry_run.
O relatório completo
GET /api/v1/campaigns/CAMP_ID/stats retorna tudo o que a visualização de campanha do Mailcheer mostra, para você exibir no seu próprio software — um CRM, um painel:
{
"campaign": { "id": "…", "name": "…", "subject": "…", "status": "sent", "kind": "newsletter",
"fromName": "…", "fromEmail": "…", "sentAt": "…", "scheduledAt": null, "updatedAt": "…" },
"counts": { "recipients": 66, "delivered": 60, "opened": 30, "clicked": 10,
"humanOpened": 22, "humanClicked": 7,
"bounced": 6, "complained": 1, "unsubscribed": 0 },
"rates": { "delivered": 0.9091, "opened": 0.5, "clicked": 0.1667,
"humanOpened": 0.3667, "humanClicked": 0.1167, "bounced": 0.0909 },
"bots": { "opened": 9, "clicked": 4, "delay": 12, "scanner": 1 },
"timeline": [ { "label": "+0h", "opened": 12, "clicked": 5, "humanOpened": 4, "humanClicked": 1 }, … ],
"audience": { "total": 30, "proxiedShare": 0.4,
"device": [ { "label": "Phone", "count": 15, "share": 0.5 }, … ],
"os": [ … ], "client": [ … ] },
"links": [ { "url": "https://…", "clicks": 6 }, … ],
"html": "<!doctype html>…"
}
As taxas (rates, share, proxiedShare) ficam entre 0 e 1, e null enquanto não houver nada para dividir. opened e clicked incluem bots, humanOpened e humanClicked os excluem; untracked conta mensagens entregues que não carregavam pixel de rastreamento de abertura — os números e taxas de abertura cobrem apenas as outras, e rates.opened é null quando nenhuma carregou; bots informa quantas aberturas e cliques foram excluídos, contados como eventos, e por quê (delay: dentro de dois minutos após a entrega; scanner: um robô que se declara ou um endereço que seu operador publica). timeline conta aberturas e cliques em janelas de seis horas durante as primeiras 48 horas após o envio, com e sem bots — vazio até a campanha sair. audience conta apenas aberturas por pessoas e mantém as cinco primeiras linhas de cada detalhamento; proxiedShare é a parcela dessas aberturas vinda de um relay de privacidade (Apple Mail, Gmail): recebido, não necessariamente lido. links é ordenado do mais para o menos clicado, por pessoas. html é a mensagem como foi enviada, string vazia caso contrário.
Quem abriu: os destinatários
GET /api/v1/campaigns/CAMP_ID/recipients retorna uma linha por pessoa para quem a campanha foi, com paginação por cursor como /subscribers (?limit= até 100, ?cursor= = o next_cursor da página anterior):
curl "https://mailcheer.com/api/v1/campaigns/CAMP_ID/recipients?status=opened" \
-H "Authorization: Bearer mch_live_…"
{
"object": "list",
"data": [
{ "object": "recipient", "id": "…", "email": "claire@example.com",
"first_name": "Claire", "last_name": "Martin", "status": "clicked",
"sent_at": "…", "delivered_at": "…", "opened_at": "…", "clicked_at": "…",
"human_opened": true, "human_clicked": true, "unsubscribed": false },
{ "object": "recipient", "id": "…", "email": null, … }
],
"has_more": true,
"next_cursor": "…"
}
?status= filtra por opened, clicked, not_opened (enviado, sem bounce, nunca abriu), bounced, complained ou unsubscribed. opened_at e clicked_at incluem bots, como os opened e clicked do relatório; human_opened e human_clicked informam se uma pessoa abriu ou clicou. email é null quando o contato foi excluído após o envio: a linha permanece, ainda conta nas métricas da campanha. unsubscribed é true quando a pessoa cancelou a assinatura pelo link deste e-mail: o link de cancelamento identifica o e-mail que o carrega, e um cancelamento feito em outro lugar (pela API, no aplicativo, por uma automação) não conta em nenhuma campanha. Para um e-mail enviado antes de 24 de setembro de 2026, cujo link identificava apenas a pessoa, o cancelamento ainda é atribuído ao último e-mail recebido antes dele. O relatório (/stats) conta as mesmas pessoas em counts.unsubscribed.
A linguagem das respostas
As mensagens de erro seguem seu cabeçalho Accept-Language: inglês por padrão, francês se você pedir.
curl https://mailcheer.com/api/v1/me
# {"error":{"code":"missing_api_key","message":"Missing API key. Add the header …"}}
curl https://mailcheer.com/api/v1/me -H "Accept-Language: fr"
# {"error":{"code":"missing_api_key","message":"Clé d'API absente. Ajoutez l'en-tête …"}}
Isso cobre tudo o que um programa lê: mensagens de erro da API, as ferramentas do servidor MCP (seus nomes, o que fazem, seus parâmetros) e a referência servida em mailcheer://docs.
Apenas a primeira preferência é lida: fr-FR,fr;q=0.9,en;q=0.8 pede francês, mesmo que o inglês esteja listado — e en-US,fr;q=0.9 pede inglês.
⚠️ O code nunca muda de idioma — escreva sua lógica contra ele, nunca contra a mensagem.
Erros
Sempre o mesmo formato, legível de duas maneiras a partir do mesmo conteúdo. O message segue o cabeçalho Accept-Language, inglês por padrão — sua lógica deve ler code (ou name), nunca message.
{
"error": {
"code": "unverified_from_domain",
"message": "Domain “example.com” is not verified in this workspace. Verified domains: yourdomain.com.",
"details": { "from": "hello@example.com", "verifiedDomains": ["yourdomain.com"] }
},
"statusCode": 422,
"message": "Domain “example.com” is not verified in this workspace. Verified domains: yourdomain.com.",
"name": "unverified_from_domain"
}
error é o formato do Mailcheer: estruturado, com details contendo o que você precisa para corrigir o problema. Os três campos simples — statusCode, message, name — correspondem ao formato do Resend, para que código escrito contra a API antiga mostre uma mensagem correta sem ser reescrito.
Escreva sua lógica contra code (ou name — são o mesmo valor), nunca contra message. A mensagem é para um humano ler, e reservamos o direito de reformulá-la.
| Código | Status | O que significa |
|---|---|---|
missing_api_key | 401 | Sem cabeçalho Authorization. |
invalid_api_key | 401 | Chave desconhecida. |
revoked_api_key | 401 | Chave revogada nas configurações. |
insufficient_scope | 403 | A chave não possui a permissão solicitada. |
reputation_blocked | 403 | Seus envios estão bloqueados: muitos rejeitados ou reclamações. |
sending_blocked | 403 | O envio está pausado para o workspace (suspenso, bloqueado ou banido): nada sai, por qualquer canal. details.reason indica qual. |
commitment_required | 403 | O compromisso antispam não foi aceito: ele é exibido na próxima vez que você entrar no workspace. |
sending_paused | 423 | O workspace está pausado para uma revisão de segurança. Não há nada de errado com a chamada: envie novamente, sem alterações, após a decisão. |
daily_quota_exceeded | 429 | Um novo workspace atingiu seu limite diário: 100 e-mails por 24 horas contínuas nos primeiros três dias, 500 até o sétimo. Retry-After e details indicam quando tentar novamente. |
quota_exceeded | 402 | A cota mensal do plano não cobre o envio: nada foi enviado. details fornece plan, quota, sent, in_flight, remaining, requested e resets_at. |
free_plan_domain_used | 402 | Workspace gratuito: o domínio de envio já atendeu ao plano gratuito este mês em outro workspace. Nada foi enviado. details fornece domain, root_domain, period e resets_at. |
not_found | 404 | O objeto não existe neste workspace. |
conflict | 409 | Estado incompatível: campanha já enviada, assinante já removido. |
idempotency_key_reused | 409 | Mesmo Idempotency-Key, corpo diferente. |
validation_error | 422 | Um campo está ausente ou malformado. |
unverified_from_domain | 422 | O domínio from não está verificado. |
suppressed_recipient | 422 | Um destinatário está na lista de supressão. |
rate_limit_exceeded | 429 | Mais de 600 solicitações por minuto (veja Retry-After). |
send_failed | 502 | Nosso provedor de envio recusou a mensagem. |
internal_error | 500 | Uma falha do nosso lado. |
Limite de taxa
600 solicitações por minuto por chave. Cada resposta inclui X-RateLimit-Limit, X-RateLimit-Remaining e X-RateLimit-Reset; uma recusa também inclui Retry-After, em segundos. Não confunda esses cabeçalhos com os de cota mensal (Mailcheer-Quota-*, acima): a taxa é contada por minuto, a cota por mês.
Precisa de mais? Escreva para nós: analisamos seu caso em vez de deixá-lo tentando novamente em um loop.
O servidor MCP
MCP — Model Context Protocol — é como um agente de IA descobre as ferramentas de um produto e as utiliza. Mailcheer expõe um servidor MCP hospedado: nada para instalar, um endereço e sua chave.
Claude Code
claude mcp add mailcheer \
--transport http \
--url https://mailcheer.com/api/mcp \
--header "Authorization: Bearer mch_live_…"
ChatGPT, Cursor, Codex, Claude Desktop — todos leem a mesma configuração de conector:
{
"mcpServers": {
"mailcheer": {
"type": "http",
"url": "https://mailcheer.com/api/mcp",
"headers": { "Authorization": "Bearer mch_live_…" }
}
}
}
Depois, no seu agente: "Qual workspace do Mailcheer você vê e quais domínios de envio estão verificados?" Ele chamará get_account, que não modifica nada — a maneira certa de confirmar uma conexão.
Ferramentas expostas
| Ferramenta | O que faz |
|---|---|
get_account | O workspace, permissões, uso deste mês (fila e data de redefinição incluídas), cobrança, limites, domínios verificados. |
send_email | Envia um e-mail transacional. Irreversível. |
get_email | O status de um e-mail enviado. |
list_subscribers | Lista assinantes, página por página. |
add_subscriber | Adiciona ou atualiza um assinante. |
remove_subscriber | Cancela a inscrição e bloqueia o endereço. Irreversível. |
list_suppression | Endereços que não receberão mais nada. |
add_suppression | Bloqueia um endereço. Irreversível. |
list_campaigns | As campanhas do workspace. |
create_campaign | Cria um rascunho. Nada é enviado. |
preview_campaign_send | Diz se a campanha seria enviada e para quantas pessoas, endereço por endereço com to. Nada é enviado. |
send_campaign | Envia para todos os assinantes ativos, ou apenas para os assinantes ativos entre os endereços em to. Irreversível. |
get_campaign_stats | Números e status de uma campanha. |
Cada ferramenta é uma chamada à API acima, nada mais: mesmas permissões, mesma cota, mesma lista de supressão, mesmas recusas. Um segundo caminho de acesso com lógica própria seria um segundo conjunto de regras, e no dia em que uma delas mudasse, o MCP se tornaria a porta dos fundos.
Nenhuma ferramenta remove um endereço da lista de supressão. É a única ação do produto que suspende uma capacidade de envio, e um agente instruído a "limpar a lista" o faria sem hesitação. Isso é feito manualmente, no seu workspace.
O recurso mailcheer://docs dá ao agente a referência completa: ele não precisa conhecê-la antecipadamente.
Se você está migrando do Resend
Os campos de POST /v1/emails e a resposta { id } são os mesmos. Na prática: a URL base e a chave.
Duas maneiras de mudar.
Com o cliente mínimo — um arquivo para copiar, sem dependências, com a mesma assinatura do SDK do Resend. Obtenha: mailcheer.com/mailcheer-client.ts.
// before
const resend = new Resend(process.env.RESEND_API_KEY);
// after
const mailcheer = new Mailcheer(process.env.MAILCHEER_API_KEY);
// the rest of your code stays the same
const { data, error } = await mailcheer.emails.send({ from, to, subject, html, text });
if (error) throw new Error(\`Email delivery failed: ${error.message}\`);
return { providerId: data?.id ?? null };
Ele nunca lança exceções: uma falha de rede também se torna um error, com name: "network_error". Isso é intencional — um método que lança exceções onde o antigo retornava um objeto transformaria "mudar duas linhas" em "revisar cada ponto de chamada", e os pontos de chamada que você esquece de revisar são exatamente os caminhos de erro.
Sem copiar nada — um fetch simples é suficiente:
const res = await fetch("https://mailcheer.com/api/v1/emails", {
method: "POST",
headers: {
Authorization: \`Bearer ${process.env.MAILCHEER_API_KEY}\`,
"Content-Type": "application/json",
},
body: JSON.stringify({ from, to, subject, html }),
});
const body = await res.json();
if (!res.ok) throw new Error(body.message); // the message is human-readable
const id = body.id;
Três coisas para saber:
- O domínio
fromdeve ser verificado no seu workspace do Mailcheer, não no Resend. Adicione-o em Domínios e publique os registros DNS. - A lista de supressão protege também os envios transacionais. Um endereço que cancelou a inscrição da sua newsletter não receberá seus e-mails transacionais do mesmo workspace — se isso não é o que você quer, separe os dois em dois workspaces.
- A cota mensal é compartilhada com suas campanhas.
Onde esses e-mails vivem
E-mails enviados via API não entram nas suas campanhas: eles vivem separadamente, e isso não é um detalhe técnico.
Um destinatário de fatura não é um assinante. Agrupá-los com seus assinantes os teria inscrito na sua lista sem que eles jamais consentissem em receber sua newsletter — contados no seu painel e segmentados pela sua próxima campanha. Seus números de assinantes permanecem os de seus assinantes reais.
O que é compartilhado: a cota mensal, a lista de supressão e o monitoramento de rejeições. Essas são as três coisas que comprometem sua reputação de remetente, e essa reputação é a mesma nos dois lados.
A especificação técnica
O arquivo OpenAPI 3.1 é servido como está: mailcheer.com/openapi.json. Ele descreve cada endpoint, cada campo e cada erro — suficiente para gerar um cliente no seu idioma ou para entregar a um agente.