# Couryo > Couryo é uma API de e-mail transacional e de produto da Wunka, feita para desenvolvedores e agentes de IA. Envio por API REST (https://api.couryo.com/v1) ou pelo servidor MCP remoto (https://mcp.couryo.com), com SMTP em breve. DNS automático, cada devolução explicada em linguagem simples, níveis de confiança públicos e pausas sempre explicadas. Preço em reais, Pix, boleto, nota fiscal e LGPD no Brasil; dólar no exterior. - Autenticação: `Authorization: Bearer ck_live_...` (produção) ou `ck_test_...` (teste: nunca entrega, status `captured`). - Use sempre o cabeçalho `Idempotency-Key` em POST: mesma chave e mesmo corpo em 24 h devolvem a mesma resposta; corpo diferente devolve 409. - Envio: `POST /v1/emails` responde 202 com `{ id, status }` só quando o e-mail entrou na fila. Lote: `POST /v1/emails/batch`, até 100. - Erros: `{ error: { type, code, message, param, doc_url, request_id } }`, com `code` estável. - Webhooks no padrão Standard Webhooks (HMAC-SHA256, cabeçalhos webhook-id, webhook-timestamp e webhook-signature). - Domínio: DKIM por CNAME em couryo1._domainkey e couryo2._domainkey, return path por CNAME em bounces. para rp.couryo.com e DMARC; sem SPF no domínio principal. - MCP remoto em https://mcp.couryo.com com a chave de API (`Authorization: Bearer ck_...`): send_email, get_email, domain_health e why_bounced. OAuth em breve. - Toda página da documentação existe em Markdown: acrescente `.md` ao endereço. - Preços: Grátis: 3.000 e-mails por mês (até 100 por dia) e 1 domínio, sem cartão. Pro: R$ 99 por mês com 50.000 e-mails e excedente de R$ 1,80 por mil. Escala: R$ 299 por mês com 200.000 e-mails e excedente de R$ 1,40 por mil. Empresa: sob medida. Fora do Brasil, em dólar: Pro US$ 19 (US$ 0,35 por mil extra) e Escala US$ 59 (US$ 0,28 por mil extra). --- # Guia do desenvolvedor > Tudo para enviar e-mail transacional pelo Couryo, pela API REST ou pelo seu agente de IA (MCP). Fonte: https://couryo.com/docs O Couryo é uma API de e-mail transacional e de produto. Você envia pela **API REST** (`https://api.couryo.com/v1`) ou pelo seu agente de IA com o [servidor MCP](https://couryo.com/docs/mcp.md) (`https://mcp.couryo.com`), acompanha cada mensagem numa linha do tempo com a resposta original do servidor de destino e recebe os eventos por webhook. O [SMTP](https://couryo.com/docs/smtp.md) chega em breve. ## Por onde começar - [Início rápido](https://couryo.com/docs/quickstart.md): o primeiro e-mail em 5 minutos. - [Autenticação e chaves](https://couryo.com/docs/authentication.md): chaves `ck_live_` e `ck_test_`, escopos e IPs permitidos. - [Enviar e-mail](https://couryo.com/docs/sending.md): idempotência, lote, agendamento, tags e anexos. - [Domínios e DNS](https://couryo.com/docs/domains.md): o que cada registro faz e como verificar. - [Webhooks](https://couryo.com/docs/webhooks.md): eventos assinados no padrão Standard Webhooks. - [Erros](https://couryo.com/docs/errors.md): todos os códigos, com causa e correção. ## Princípios da API - **REST com JSON**, datas em ISO 8601 UTC (`2026-10-07T18:30:00Z`). - **IDs com prefixo** e aleatórios: `msg_` (e-mail), `dom_` (domínio), `key_` (chave), `whk_` (webhook), `evt_` (evento), sempre com 16 caracteres depois do prefixo. - **Nunca há sucesso falso.** A resposta `202` só sai quando o e-mail entrou na fila. Se algo impede o envio, você recebe um erro com código estável e explicação. - **Idempotência em todo POST**, com o cabeçalho `Idempotency-Key`. - **Paginação por cursor**: `?limit=25&starting_after=` devolve `{ "data": [...], "has_more": true }`. - **Mensagens em português ou inglês**, conforme o cabeçalho `Accept-Language` (o padrão é `pt-BR`). ## Endpoints | Método e caminho | O que faz | |---|---| | `POST /v1/emails` | envia um e-mail | | `POST /v1/emails/batch` | envia até 100 e-mails numa chamada | | `GET /v1/emails` e `GET /v1/emails/{id}` | lista e-mails ou traz um, com a linha do tempo | | `POST /v1/emails/check` | checa um e-mail antes de enviar, sem enviar | | `GET` e `POST /v1/domains` | lista e cria domínios | | `GET` e `DELETE /v1/domains/{id}`, `POST /v1/domains/{id}/verify` | consulta, apaga e verifica um domínio | | `GET` e `POST /v1/webhooks`, `GET`, `PATCH` e `DELETE /v1/webhooks/{id}` | webhooks | | `GET` e `POST /v1/suppressions`, `DELETE /v1/suppressions/{email}` | lista de supressão | | `GET /v1/events` | eventos, para quem perdeu um webhook | | `GET /openapi.json` | a especificação OpenAPI 3.1 da API, sem chave | ## Para agentes de IA Esta documentação inteira existe em Markdown. Acrescente `.md` a qualquer endereço (por exemplo, [/docs/quickstart.md](https://couryo.com/docs/quickstart.md.md)) ou use o [llms.txt](https://couryo.com/llms.txt) e o [llms-full.txt](https://couryo.com/llms-full.txt). Os preços estão em [/precos.md](https://couryo.com/precos.md). --- # Início rápido > Envie o primeiro e-mail pelo Couryo em 5 minutos, da criação da conta à linha do tempo da mensagem. Fonte: https://couryo.com/docs/quickstart Em 5 minutos você cria a conta, verifica um domínio, gera uma chave e manda o primeiro e-mail. ## 1. Crie a conta Entre em [app.couryo.com](https://app.couryo.com) com a sua conta Google. Toda conta começa no plano Grátis e no **nível 0 (sandbox)**: até 25 e-mails por dia, só para os e-mails da própria conta, o suficiente para testar a integração (antes de verificar um domínio, use o remetente `teste@sandbox.couryo.com`). Veja os [níveis de confiança](https://couryo.com/docs/limits.md). ## 2. Adicione e verifique o domínio No painel, em **Domínios**, adicione o domínio de onde os e-mails vão sair (por exemplo, `exemplo.com.br`). - **Usa a Cloudflare?** Clique em **Conectar Cloudflare**. O Couryo cria os registros sozinho. - **Outro painel (Registro.br, por exemplo)?** Copie cada registro mostrado na tela. O status de cada um atualiza ao vivo. Quando DKIM, return path e DMARC estiverem certos, o domínio fica `verified` e a conta sobe para o nível 1 (100 por dia). Não é preciso mexer no SPF do domínio principal. O plano Grátis permite 1 domínio. Detalhes em [Domínios e DNS](https://couryo.com/docs/domains.md). ## 3. Crie uma chave de API Em **Chaves**, crie uma chave com escopo `send`. A chave inteira aparece **uma única vez**: guarde no seu gerenciador de segredos ou no `.env`. ```bash title="Terminal" export COURYO_API_KEY="ck_live_..." ``` Quer testar sem entregar nada? Use uma chave `ck_test_`. Veja [Modo de teste](https://couryo.com/docs/test-mode.md). ## 4. Envie ```bash title="cURL" curl https://api.couryo.com/v1/emails \ -H "Authorization: Bearer $COURYO_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: boas-vindas-usr-123" \ -d '{ "from": "Exemplo ", "to": ["voce@exemplo.com.br"], "subject": "Olá do Couryo", "html": "

Funcionou!

", "text": "Funcionou!" }' ``` ```js title="Node.js" const res = await fetch("https://api.couryo.com/v1/emails", { method: "POST", headers: { Authorization: `Bearer ${process.env.COURYO_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": "boas-vindas-usr-123", }, body: JSON.stringify({ from: "Exemplo ", to: ["voce@exemplo.com.br"], subject: "Olá do Couryo", html: "

Funcionou!

", text: "Funcionou!", }), }); console.log(res.status, await res.json()); ``` A resposta é `202 Accepted`: ```json title="Resposta" { "id": "msg_9w2k7c1x0d4e5f6g", "status": "queued" } ``` ## 5. Acompanhe a entrega A chave `send` só envia. Para ler a linha do tempo, use uma chave com escopo `read` (ou abra o e-mail no painel): ```bash title="cURL" curl https://api.couryo.com/v1/emails/msg_9w2k7c1x0d4e5f6g \ -H "Authorization: Bearer $COURYO_READ_KEY" ``` O campo `events` traz cada etapa (`accepted`, `queued`, `sent`, `delivered`...) com horário, servidor de destino, código SMTP e uma explicação em linguagem simples. Veja [Eventos e linha do tempo](https://couryo.com/docs/events.md). ## Próximos passos - Receba os eventos no seu sistema com [webhooks](https://couryo.com/docs/webhooks.md). - Evite envios duplicados com [idempotência](https://couryo.com/docs/sending.md#idempotencia). - Use o Couryo pelo seu agente de IA com o [servidor MCP](https://couryo.com/docs/mcp.md). - Prefere não mexer no código? O [SMTP](https://couryo.com/docs/smtp.md) chega em breve. > SDKs oficiais para Node e PHP estão a caminho. Até lá, qualquer cliente HTTP funciona, como nos exemplos acima. --- # Autenticação e chaves > Como autenticar na API do Couryo com chaves ck_live_ e ck_test_, escopos, IPs permitidos e boas práticas. Fonte: https://couryo.com/docs/authentication Toda chamada para `https://api.couryo.com/v1` usa uma chave de API no cabeçalho `Authorization`: ```http title="Cabeçalho" Authorization: Bearer ck_live_... ``` ## Tipos de chave | Prefixo | Modo | O que acontece | |---|---|---| | `ck_live_` | produção | entrega de verdade | | `ck_test_` | teste | nunca entrega: o e-mail fica capturado no painel com status `captured` | ## Escopos | Escopo | Pode | |---|---| | `send` | enviar e checar e-mails (`POST /v1/emails`, `/v1/emails/batch` e `/v1/emails/check`); não lê nada | | `read` | ler e-mails, eventos, domínios, webhooks e supressões; não envia | | `admin` | tudo, inclusive criar e verificar domínios, criar e mudar webhooks e mexer na lista de supressão | Use o menor escopo possível. O servidor da sua aplicação normalmente só precisa de `send`; para consultar a linha do tempo de um e-mail, use uma chave `read`. As chaves valem para um projeto: cada projeto tem as suas. ## IPs permitidos Cada chave pode ter uma lista de IPs permitidos (endereços ou faixas CIDR). Uma chamada de outro endereço recebe `403` com o código `ip_not_allowed`. ## Como guardamos sua chave - A chave inteira aparece **uma única vez**, na criação. Depois, o painel mostra só o prefixo (por exemplo, `ck_live_4f9a`). - O Couryo guarda apenas um hash da chave. Se perder, crie outra e apague a antiga. - Uma chave apagada para de funcionar na hora (`401 invalid_api_key`). ## Boas práticas - Guarde a chave em variável de ambiente ou num gerenciador de segredos. Nunca no código do navegador ou do app móvel. - Uma chave por aplicação e por ambiente: fica fácil trocar uma sem derrubar as outras. - Proteja a conta Google que você usa para entrar no painel com verificação em duas etapas e defina um [limite de gasto](https://couryo.com/docs/limits.md#limite-de-gasto). ## Erros de autenticação | HTTP | `code` | Quando | |---|---|---| | 401 | `missing_api_key` | sem o cabeçalho `Authorization` | | 401 | `invalid_api_key` | chave inexistente, apagada ou mal copiada | | 403 | `insufficient_scope` | a chave não tem o escopo necessário | | 403 | `ip_not_allowed` | a chamada veio de um IP fora da lista | Veja todos os códigos em [Erros](https://couryo.com/docs/errors.md). --- # Modo de teste > Chaves ck_test_ capturam os e-mails sem entregar e simulam entrega, devolução, reclamação e adiamento pelo endereço do destinatário, com eventos e webhooks de verdade. Fonte: https://couryo.com/docs/test-mode Com uma chave `ck_test_`, o Couryo faz tudo o que faria de verdade (valida o pedido, aplica idempotência, gera os eventos e chama seus webhooks), **menos entregar**. Nenhum e-mail sai para a internet. ## Como usar 1. Crie uma chave de teste em **Chaves** (modo `test`). 2. Use a chave no lugar da `ck_live_`, sem mudar mais nada no código. 3. A resposta traz `status: "captured"`: ```json title="Resposta 202" { "id": "msg_t3st0k9a1b2c3d4e", "status": "captured" } ``` O status do e-mail continua `captured` para sempre; o que aconteceu com cada destinatário aparece nos eventos. ## Endereços simulados A parte antes do `@` do destinatário decide o que o teste simula, em qualquer domínio: | Destinatário | O que acontece | Eventos | |---|---|---| | `bounced@...` ou `bounce@...` | devolução permanente | `sent` e `bounced` com `550 5.1.1` (usuário desconhecido) | | `complained@...` ou `complaint@...` | entregue e depois marcado como spam | `sent`, `delivered` e `complained` | | `deferred@...` | adiado e depois entregue | `sent`, `deferred` com `421 4.7.0` e `delivered` | | qualquer outro | entregue | `sent` e `delivered` com `250 2.0.0` | Os eventos chegam logo após o envio, com horários (`at`) que imitam o ritmo real: a reclamação 5 segundos depois da entrega e a entrega do adiado 1 minuto depois do adiamento. ```bash title="cURL" curl https://api.couryo.com/v1/emails \ -H "Authorization: Bearer $COURYO_TEST_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "Loja ", "to": ["bounced@exemplo.com.br", "ana@exemplo.com.br"], "subject": "Teste de devolução", "text": "Olá" }' ``` Os eventos simulados passam pelo mesmo caminho dos reais: aparecem na linha do tempo (`GET /v1/emails/{id}`), em `GET /v1/events` e nos seus [webhooks](https://couryo.com/docs/webhooks.md), com resposta SMTP marcada como simulada e a explicação em linguagem simples. É o jeito de testar o tratamento de devolução e reclamação sem estragar a reputação de ninguém. ## O que muda em relação à chave de produção - **Não exige domínio verificado** no `from` e não tem a restrição do nível 0 (sandbox). - **Não conta** nos limites diário e mensal do nível nem na cota do plano. O limite de requisições por segundo vale igual. - **Não mexe na supressão** nem nas taxas de devolução e reclamação da conta: um `bounced@` simulado não entra na sua lista de supressão. - A checagem de conteúdo (links encurtados e afins) só roda em produção. Para conferir um e-mail antes, use [`POST /v1/emails/check`](https://couryo.com/docs/sending.md#checagem-antes-do-envio). ## No painel O e-mail aparece em **E-mails** com o status `captured` e a linha do tempo simulada, e as chamadas de webhook ficam no histórico de entregas, para conferir sua integração. Ver o conteúdo renderizado e compartilhar um link de revisão com o time: em breve. ## Bom para - Testes automatizados e CI: nenhum e-mail escapa para clientes reais. - Ambiente de homologação com dados copiados da produção. - Testar o tratamento de webhooks de devolução, reclamação e adiamento. - Usar o [MCP](https://couryo.com/docs/mcp.md) com o seu agente sem risco. --- # Enviar e-mail > POST /v1/emails em detalhe. Campos, destinatários em texto ou lista, idempotência, envio em lote, agendamento, tags, metadados, anexos e checagem antes do envio. Fonte: https://couryo.com/docs/sending `POST /v1/emails` envia um e-mail (escopo `send`). A resposta `202 Accepted` traz o `id` e o `status`, e só sai **depois** de o e-mail entrar na fila. Se algo impede o envio, você recebe um [erro](https://couryo.com/docs/errors.md), nunca um sucesso falso. ```json title="Resposta 202" { "id": "msg_9w2k7c1x0d4e5f6g", "status": "queued" } ``` Com uma chave `ck_test_`, o status é `captured` e nada é entregue (veja [Modo de teste](https://couryo.com/docs/test-mode.md)). ## Campos | Campo | Tipo | Obrigatório | Descrição | |---|---|---|---| | `from` | string | sim | remetente num domínio verificado do projeto: `"oi@exemplo.com.br"` ou `"Loja "`. Subdomínio de um domínio verificado também vale | | `to` | string ou string[] | sim | um endereço (`"ana@exemplo.com.br"`) ou uma lista | | `cc`, `bcc` | string ou string[] | não | cópia e cópia oculta, também em texto ou lista | | `reply_to` | string ou string[] | não | para onde vão as respostas | | `subject` | string | sim | assunto, de 1 a 998 caracteres | | `html` | string | um dos dois | corpo em HTML (até 5 MB) | | `text` | string | um dos dois | corpo em texto puro (até 5 MB) | | `attachments` | objeto[] | não | até 20 anexos (veja abaixo) | | `headers` | objeto | não | cabeçalhos extras, como `{ "X-Pedido": "1042" }` | | `tags` | objeto | não | pares chave e valor em texto para filtrar e agrupar | | `metadata` | objeto | não | dados seus em texto, devolvidos nos webhooks | | `scheduled_at` | string ISO 8601 | não | enviar mais tarde, até 30 dias à frente | | `stream` | `transactional` ou `marketing` | não | fluxo; o padrão é `transactional` | | `template` + `variables` | string + objeto | não | templates salvos: em breve (hoje a API responde `invalid_field`; mande `html` ou `text`) | - **No máximo 50 destinatários** por e-mail, somando `to`, `cc` e `bcc`. Para mais, use o [lote](#envio-em-lote). - O corpo da chamada pode ter até 30 MB, contando os anexos em Base64. - Mande sempre `text` junto com `html`. Provedores confiam mais em e-mails com as duas partes, e quem lê em texto puro agradece. ```json title="Corpo com destinatários em texto e em lista" { "from": "Loja Exemplo ", "to": "ana@exemplo.com.br", "cc": ["financeiro@exemplo.com.br", "logistica@exemplo.com.br"], "reply_to": "atendimento@exemplo.com.br", "subject": "Pedido 1042 confirmado", "html": "

Seu pedido 1042 foi confirmado.

", "text": "Seu pedido 1042 foi confirmado.", "tags": { "tipo": "pedido" }, "metadata": { "pedido_id": "1042" } } ``` ## Idempotência Rede cai, função reinicia, fila repete. Para não mandar o mesmo e-mail duas vezes, envie o cabeçalho `Idempotency-Key` (até 255 caracteres) com um valor único para a operação de negócio: ```http title="Cabeçalho" Idempotency-Key: pedido-1042-confirmacao ``` - Por **24 horas**, repetir a chamada com a mesma chave e o **mesmo corpo** devolve a **mesma resposta**, com o cabeçalho `Idempotent-Replayed: true`, sem enviar de novo. - A mesma chave com um **corpo diferente** devolve `409 idempotency_conflict`. - Se o primeiro pedido ainda estiver em andamento, a repetição recebe `409 idempotency_in_progress` com `Retry-After: 1`. - Respostas `5xx`, `409` e `429` não ficam guardadas: dá para repetir com a mesma chave. - Funciona em todo `POST`, inclusive no lote. Use algo ligado ao evento, como `pedido-{id}-confirmacao` ou `senha-{usuario}-{timestamp}`. Um UUID novo a cada tentativa não protege contra repetição. ## Destinatários suprimidos Se **alguns** destinatários estão na [lista de supressão](https://couryo.com/docs/suppressions.md), o e-mail sai para os outros e a linha do tempo ganha um evento `suppressed` para cada endereço pulado. Se **todos** estão suprimidos, a resposta é `422 recipient_suppressed`, com `param` apontando o primeiro (`to[0]`). ## Envio em lote `POST /v1/emails/batch` aceita até **100 e-mails** por chamada, no campo `emails`. Cada item tem os mesmos campos de `POST /v1/emails` e é validado sozinho: um item com problema não derruba os outros. ```bash title="cURL" curl https://api.couryo.com/v1/emails/batch \ -H "Authorization: Bearer $COURYO_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: lembretes-2026-10-07" \ -d '{ "emails": [ { "from": "oi@exemplo.com.br", "to": "ana@exemplo.com.br", "subject": "Lembrete", "text": "Sua aula começa às 19h." }, { "from": "oi@exemplo.com.br", "to": ["bruno@exemplo.com.br"], "subject": "Lembrete", "text": "Sua aula começa às 19h." } ] }' ``` A resposta é `200` com um resultado por item, na mesma ordem. O `param` dos erros traz o índice do item: ```json title="Resposta 200" { "data": [ { "id": "msg_4h8s1m2p0q7r6t5u", "status": "queued" }, { "error": { "type": "invalid_request", "code": "recipient_suppressed", "message": "O endereço bruno@exemplo.com.br está na lista de supressão.", "param": "emails[1].to[0]", "doc_url": "https://couryo.com/docs/errors#recipient_suppressed", "request_id": "req_2m9x4k7c1v0b8n3q" } } ] } ``` - Mais de 100 itens: `400 batch_too_large`, e nada é enviado. - Um erro de limite (`daily_limit_reached`, `monthly_limit_reached`, `spend_limit_reached` ou `sending_paused`) para o resto do lote: o item que bateu no limite e todos os seguintes voltam com o mesmo erro. ## Agendamento Passe `scheduled_at` em ISO 8601 com fuso (`Z` ou `-03:00`), até 30 dias à frente. O e-mail fica com status `queued` até a hora marcada. Uma data no passado (mais de 1 minuto) ou além de 30 dias devolve `invalid_field`. ```json title="Corpo" { "from": "oi@exemplo.com.br", "to": "ana@exemplo.com.br", "subject": "Sua aula começa em 1 hora", "text": "Até já!", "scheduled_at": "2026-10-08T18:00:00-03:00" } ``` ## Tags e metadados - `tags` servem para filtrar a listagem (`GET /v1/emails?tag=tipo:pedido`) e agrupar no painel: `{ "tipo": "pedido", "campanha": "outubro" }`. As chaves usam letras, números, `_` e `-` (até 64); os valores, até 256 caracteres. - `metadata` é seu: volta em cada webhook, útil para ligar o e-mail a um registro do seu sistema: `{ "pedido_id": "1042" }`. Chaves até 64 caracteres; valores até 1.024. Os dois aceitam apenas valores em texto. ## Anexos Cada anexo tem `filename` (até 255 caracteres), `content` (o arquivo em Base64) e, opcionalmente, `content_type`. Até 20 por e-mail. ```json title="Anexo" { "filename": "recibo.pdf", "content": "JVBERi0xLjcK...", "content_type": "application/pdf" } ``` > Boleto e nota fiscal: mande **por link**, não como anexo. Anexo de boleto é um dos padrões mais usados em golpes, e os filtros de spam sabem disso. A checagem antes do envio avisa (`billing_attachment`). ## Cabeçalhos Os cabeçalhos de `headers` vão como você mandou, menos os que pertencem à plataforma ou quebram a autenticação, que são ignorados: `From`, `To`, `Cc`, `Bcc`, `Subject`, `Message-ID`, `Date`, `Return-Path`, `DKIM-Signature`, `List-Unsubscribe`, `List-Unsubscribe-Post`, `Content-Type`, `Content-Transfer-Encoding` e `MIME-Version`. ## Transacional e marketing O campo `stream` separa os fluxos. Descadastro de marketing nunca bloqueia e-mail transacional (senha, login, cobrança). Em `marketing`, o Couryo inclui o descadastro em um clique (`List-Unsubscribe` e `List-Unsubscribe-Post`, RFC 8058) que Gmail e Yahoo exigem, e quem clica entra na supressão do fluxo `marketing`. Abertura e clique são medidos só no fluxo `marketing`. ## Checagem antes do envio `POST /v1/emails/check` (escopo `send`) recebe o mesmo corpo do envio e devolve uma nota de 0 a 100 e a lista de problemas, sem enviar nada. Bom para rodar no CI. ```json title="Resposta 200" { "score": 90, "issues": [{ "code": "missing_text_part", "severity": "warning", "message": "O e-mail não tem parte em texto." }] } ``` | `code` | Gravidade | O que aponta | |---|---|---| | `domain_not_verified` | error | o domínio do `from` não está verificado: o envio seria recusado | | `link_shortener` | error | link encurtado; use o link completo do seu domínio | | `missing_text_part` | warning | há `html` sem `text` | | `insecure_link` | warning | links sem HTTPS | | `html_too_large` | warning | HTML acima de 100 KB (o Gmail corta a mensagem) | | `subject_all_caps` | warning | assunto todo em maiúsculas | | `billing_attachment` | warning | boleto, fatura ou nota fiscal como anexo | | `image_without_alt` | info | imagem sem texto alternativo | ## Listar e consultar | Método e caminho | Escopo | O que faz | |---|---|---| | `GET /v1/emails` | `read` | lista do mais novo para o mais antigo; filtros `status`, `recipient` (parte do endereço), `tag` (`chave:valor` ou termo livre) e `period` (`24h`, `7d`, `30d`), com `limit` e `starting_after` | | `GET /v1/emails/{id}` | `read` | um e-mail com a [linha do tempo](https://couryo.com/docs/events.md) | --- # SMTP > Em breve. O relay SMTP do Couryo, para enviar sem mudar o código. Servidor, portas, usuário, senha e exemplos para Laravel, Node e Supabase. Fonte: https://couryo.com/docs/smtp > **Em breve.** O relay SMTP ainda não está no ar: `smtp.couryo.com` não aceita conexões por enquanto. Hoje, envie pela [API REST](https://couryo.com/docs/sending.md), que já tem idempotência, lote e anexos. Esta página mostra como vai funcionar. Para apps, frameworks e ferramentas que já enviam por SMTP, vai bastar trocar as credenciais. ## Credenciais | Campo | Valor | |---|---| | Servidor | `smtp.couryo.com` | | Porta | `587` (STARTTLS), `465` (TLS direto) ou `2525` (quando a 587 está bloqueada) | | Usuário | `couryo` | | Senha | a sua chave de API (`ck_live_...` ou `ck_test_...`) com escopo `send` | O remetente precisa estar num [domínio verificado](https://couryo.com/docs/domains.md), como na API. Chaves `ck_test_` também funcionam por SMTP e capturam o e-mail sem entregar. ## Laravel ```ini title=".env" MAIL_MAILER=smtp MAIL_HOST=smtp.couryo.com MAIL_PORT=587 MAIL_USERNAME=couryo MAIL_PASSWORD=ck_live_... MAIL_ENCRYPTION=tls MAIL_FROM_ADDRESS=oi@exemplo.com.br MAIL_FROM_NAME="Exemplo" ``` ## Node.js (Nodemailer) ```js title="Node.js" import nodemailer from "nodemailer"; const transport = nodemailer.createTransport({ host: "smtp.couryo.com", port: 587, secure: false, // STARTTLS auth: { user: "couryo", pass: process.env.COURYO_API_KEY }, }); await transport.sendMail({ from: "Exemplo ", to: "ana@exemplo.com.br", subject: "Olá", text: "Enviado pelo Couryo via SMTP.", }); ``` ## Supabase Auth e outras ferramentas Em ferramentas com campo de "SMTP personalizado" (Supabase Auth, WordPress, n8n e afins), preencha servidor, porta, usuário e senha da tabela acima. ## API ou SMTP? | | API | SMTP | |---|---|---| | Idempotência | sim, com `Idempotency-Key` | não | | Resposta com o `id` da mensagem | sim | sim, na resposta do `DATA` | | Lote de até 100 numa chamada | sim | não | | Mudança no código | pequena | nenhuma | Quando o SMTP estiver no ar, os dois caminhos vão gerar os mesmos eventos, aparecer na mesma linha do tempo e chamar os mesmos webhooks. --- # Domínios e DNS > Como verificar um domínio no Couryo. Os registros DKIM (couryo1 e couryo2), return path e DMARC que a API gera, por que não há SPF no domínio principal e como o DMARC sobe até p=reject. Fonte: https://couryo.com/docs/domains Para enviar com o seu domínio no `from`, ele precisa estar verificado. Isso prova aos provedores (Gmail, Microsoft, Yahoo, UOL...) que o Couryo pode enviar em seu nome, e é o que mais pesa para o e-mail chegar na caixa de entrada. ## Adicionar um domínio Criar um domínio exige uma chave com escopo `admin` (ou o painel). ```bash title="cURL" curl https://api.couryo.com/v1/domains \ -H "Authorization: Bearer $COURYO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "exemplo.com.br" }' ``` A resposta (`201`) traz os registros que você precisa criar, cada um com o próprio `status`: ```json title="Resposta 201" { "id": "dom_7h2kq9w4x1abcdef", "name": "exemplo.com.br", "status": "pending", "dmarc_policy": "missing", "cloudflare_connected": false, "records": [ { "purpose": "dkim", "type": "CNAME", "name": "couryo1._domainkey.exemplo.com.br", "value": "couryo1.7h2kq9w4x1abcdef.dkim.couryo.com", "status": "pending" }, { "purpose": "dkim", "type": "CNAME", "name": "couryo2._domainkey.exemplo.com.br", "value": "couryo2.7h2kq9w4x1abcdef.dkim.couryo.com", "status": "pending" }, { "purpose": "return_path", "type": "CNAME", "name": "bounces.exemplo.com.br", "value": "rp.couryo.com", "status": "pending" }, { "purpose": "dmarc", "type": "TXT", "name": "_dmarc.exemplo.com.br", "value": "v=DMARC1; p=none; rua=mailto:dmarc@couryo.com; adkim=r; aspf=r", "status": "pending" } ], "created_at": "2026-10-07T18:30:00Z" } ``` O alvo dos dois DKIM segue sempre o formato `..dkim.couryo.com`. O ID acima é um exemplo: copie os valores que aparecem no painel ou na resposta da API para o seu domínio. > **Domínios por plano:** Grátis 1, Pro 10, Escala 50 e Empresa sem limite, somando todos os projetos da conta. Acima disso, a API responde `403` com o código [`domain_limit_reached`](https://couryo.com/docs/errors.md#domain_limit_reached); apagar um domínio libera a vaga. Cada domínio só pode estar cadastrado uma vez no Couryo (`409 domain_exists`). ## O que cada registro faz | Registro | Tipo | Nome | Para que serve | |---|---|---|---| | **DKIM** | CNAME | `couryo1._domainkey` e `couryo2._domainkey` | Cada e-mail sai assinado com uma chave RSA de 2048 bits do seu domínio. Os dois seletores apontam para a zona do Couryo, onde ficam as chaves públicas: assinamos com um enquanto a chave do outro é trocada, então a rotação acontece sem você mexer no DNS. | | **Return path** | CNAME | `bounces` | Endereço técnico do envelope, num subdomínio seu. É ele que leva o SPF (alinhado com o seu domínio) e faz as devoluções chegarem ao Couryo, que as transforma em eventos. | | **DMARC** | TXT | `_dmarc` | Diz aos provedores o que fazer com e-mail que finge ser do seu domínio e para onde mandar relatórios. O `rua=mailto:dmarc@couryo.com` traz esses relatórios para o Couryo. Começa em `p=none`. | | **Inbound** | MX | | Opcional, para receber e-mails no Couryo (em breve). | ### Por que não há SPF no domínio principal O SPF é conferido no domínio do envelope (o return path), não no domínio do `From`. Como o envelope usa `bounces.exemplo.com.br`, que aponta para o Couryo por CNAME, o SPF passa e fica alinhado com o seu domínio. **Não mexa no SPF de `exemplo.com.br`**: o que você já tem (Google Workspace, Microsoft 365...) continua como está, e nenhuma das 10 consultas de DNS que o SPF permite é gasta com o Couryo. > Se a resposta trouxer um registro `return_path` do tipo `MX` e um registro `spf` (`TXT v=spf1 include:spf.couryo.com ~all`), é o modo alternativo de return path. Crie os dois no subdomínio `bounces.`, nunca no domínio principal. ## Verificar Com a Cloudflare conectada, o Couryo cria os registros e verifica sozinho. Em outros painéis, crie os registros e peça a verificação (escopo `admin`): ```bash title="cURL" curl -X POST https://api.couryo.com/v1/domains/dom_7h2kq9w4x1abcdef/verify \ -H "Authorization: Bearer $COURYO_API_KEY" ``` A verificação consulta o DNS público. Cada registro volta com `status`: - `ok`: encontrado e correto. - `pending`: ainda não encontrado. A propagação de DNS pode levar de minutos a algumas horas. - `wrong`: encontrado com outro valor. O campo `found` mostra o que está publicado, para você comparar. Se você já tem um DMARC próprio, ele é respeitado: o registro fica `ok` e o campo `found` mostra o seu valor. Dois registros DMARC no mesmo nome invalidam os dois e aparecem como `wrong`. O domínio passa para `verified` quando DKIM, return path e DMARC estão `ok` e o motor do Couryo confirma a assinatura DKIM. Domínios pendentes são conferidos de novo sozinhos a cada 10 minutos por 72 horas; os verificados, uma vez por dia. O primeiro domínio verificado leva a conta do nível 0 para o nível 1 (veja [Limites e níveis](https://couryo.com/docs/limits.md)). ## Conectar a Cloudflare No painel, em **Domínios**, use **Conectar Cloudflare** e cole um token da sua conta com a permissão **Zona > DNS > Editar** para o domínio. O Couryo cria os registros na sua zona e verifica na hora. Um DMARC que já existe nunca é sobrescrito. Erros possíveis: `invalid_cloudflare_token` (token recusado ou sem a permissão) e `cloudflare_zone_not_found` (o token não alcança a zona do domínio). ## Dicas por painel - **Cloudflare (à mão):** deixe os CNAMEs com o proxy desligado (nuvem cinza). - **Registro.br:** no modo avançado de DNS, o campo de nome recebe só a parte antes do domínio (por exemplo, `couryo1._domainkey` e `bounces`). - **Outros painéis:** alguns completam o domínio sozinhos. Se o registro aparecer como `couryo1._domainkey.exemplo.com.br.exemplo.com.br`, apague o domínio repetido do nome. ## DMARC de p=none até p=reject O DMARC começa em `p=none`: os provedores só observam e mandam relatórios. Com os relatórios limpos por algumas semanas, vale subir para `p=quarantine` (e-mail falso vai para o spam) e depois para `p=reject` (e-mail falso é recusado). Conduzir essa subida com a sua permissão é o próximo passo do agente de entregabilidade (em breve). O campo `dmarc_policy` do domínio mostra a política publicada hoje: `none`, `quarantine`, `reject` ou `missing`. ## Outros endpoints | Método e caminho | Escopo | O que faz | |---|---|---| | `GET /v1/domains` | `read` | lista os domínios do projeto | | `GET /v1/domains/{id}` | `read` | um domínio, com os registros | | `DELETE /v1/domains/{id}` | `admin` | remove o domínio (`204`) e libera a vaga do plano | --- # Webhooks > Receba os eventos dos seus e-mails por webhook, assinados no padrão Standard Webhooks, com reenvio por até 3 dias. Exemplos de verificação em Node, PHP e Python. Fonte: https://couryo.com/docs/webhooks Webhooks avisam o seu sistema quando algo acontece com um e-mail: entregue, adiado, devolvido, reclamação, aberto, clicado. Cada chamada é assinada no padrão aberto [Standard Webhooks](https://www.standardwebhooks.com). ## Criar um webhook ```bash title="cURL" curl https://api.couryo.com/v1/webhooks \ -H "Authorization: Bearer $COURYO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://exemplo.com.br/webhooks/couryo", "events": ["delivered", "bounced", "complained"] }' ``` ```json title="Resposta" { "id": "whk_5n1c8v2z7r3k6m9p", "url": "https://exemplo.com.br/webhooks/couryo", "events": ["delivered", "bounced", "complained"], "enabled": true, "secret": "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw", "created_at": "2026-10-07T18:30:00Z" } ``` O `secret` aparece **só nesta resposta**. Guarde junto das suas outras credenciais. - A URL precisa começar com `https://` e ser pública: endereços de rede interna são recusados (`invalid_field`). - Criar, mudar e apagar webhooks exige escopo `admin`; listar e consultar, `read`. - `PATCH /v1/webhooks/{id}` muda `url`, `events` ou `enabled` (para pausar sem apagar). `DELETE /v1/webhooks/{id}` apaga. ## O que chega Um `POST` com JSON e três cabeçalhos: ```http title="Cabeçalhos" webhook-id: evt_7d3k9s0a2m5n8b1c webhook-timestamp: 1791484264 webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4= ``` ```json title="Corpo" { "type": "bounced", "timestamp": "2026-10-07T18:31:04Z", "data": { "email_id": "msg_9w2k7c1x0d4e5f6g", "event_id": "evt_7d3k9s0a2m5n8b1c", "recipient": "maria@empresa.com.br", "provider": "other", "mx": "mx.empresa.com.br", "smtp_code": "550 5.1.1", "smtp_response": "550 5.1.1 : Recipient address rejected: User unknown", "explanation": "A caixa de destino não existe. O endereço foi para a lista de supressão.", "tags": { "tipo": "pedido" }, "metadata": { "pedido_id": "1042" } } } ``` O `webhook-id` é o ID do evento e se repete nos reenvios: use para ignorar duplicatas. ## Verificar a assinatura Sempre verifique antes de confiar no conteúdo. A assinatura é um HMAC-SHA256 de `webhook-id.webhook-timestamp.corpo`, com o segredo decodificado de Base64 (sem o prefixo `whsec_`). Recuse também mensagens com mais de 5 minutos, para evitar repetição. > Use o **corpo cru**, exatamente como chegou. Se o seu framework já converteu para JSON, a assinatura não vai bater. ```js title="Node.js (Express)" import crypto from "node:crypto"; import express from "express"; function verifyWebhook(rawBody, headers, secret) { const id = headers["webhook-id"]; const timestamp = headers["webhook-timestamp"]; const signatures = headers["webhook-signature"]; if (!id || !timestamp || !signatures) throw new Error("missing headers"); if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) throw new Error("timestamp too old"); const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64"); const expected = crypto.createHmac("sha256", key).update(`${id}.${timestamp}.${rawBody}`).digest("base64"); const valid = signatures.split(" ").some((entry) => { const [version, signature] = entry.split(","); return ( version === "v1" && signature?.length === expected.length && crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)) ); }); if (!valid) throw new Error("invalid signature"); return JSON.parse(rawBody); } const app = express(); app.post("/webhooks/couryo", express.raw({ type: "application/json" }), (req, res) => { let event; try { event = verifyWebhook(req.body.toString("utf8"), req.headers, process.env.COURYO_WEBHOOK_SECRET); } catch { return res.sendStatus(400); } res.sendStatus(200); // responda logo; processe depois (fila, job) console.log(event.type, event.data.email_id); }); ``` ```php title="PHP" function verify_webhook(string $body, array $headers, string $secret): array { $id = $headers['webhook-id'] ?? ''; $timestamp = $headers['webhook-timestamp'] ?? ''; $signatures = $headers['webhook-signature'] ?? ''; if ($id === '' || $timestamp === '' || $signatures === '') { throw new RuntimeException('missing headers'); } if (abs(time() - (int) $timestamp) > 300) { throw new RuntimeException('timestamp too old'); } $key = base64_decode(preg_replace('/^whsec_/', '', $secret)); $expected = base64_encode(hash_hmac('sha256', "{$id}.{$timestamp}.{$body}", $key, true)); foreach (explode(' ', $signatures) as $entry) { [$version, $signature] = array_pad(explode(',', $entry, 2), 2, ''); if ($version === 'v1' && hash_equals($expected, $signature)) { return json_decode($body, true); } } throw new RuntimeException('invalid signature'); } $body = file_get_contents('php://input'); $headers = array_change_key_case(getallheaders(), CASE_LOWER); try { $event = verify_webhook($body, $headers, getenv('COURYO_WEBHOOK_SECRET')); } catch (RuntimeException $e) { http_response_code(400); exit; } http_response_code(200); ``` ```python title="Python" import base64 import hashlib import hmac import json import time def verify_webhook(body: bytes, headers, secret: str) -> dict: msg_id = headers.get("webhook-id") timestamp = headers.get("webhook-timestamp") signatures = headers.get("webhook-signature") if not (msg_id and timestamp and signatures): raise ValueError("missing headers") if abs(time.time() - int(timestamp)) > 300: raise ValueError("timestamp too old") key = base64.b64decode(secret.removeprefix("whsec_")) signed = f"{msg_id}.{timestamp}.".encode() + body expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode() for entry in signatures.split(" "): version, _, signature = entry.partition(",") if version == "v1" and hmac.compare_digest(signature, expected): return json.loads(body) raise ValueError("invalid signature") ``` As bibliotecas oficiais do Standard Webhooks (para Node, PHP, Python, Go, Ruby e outras) também funcionam com o segredo do Couryo. ## Reenvio - Responda com qualquer status `2xx` para confirmar. Responda rápido e processe depois, numa fila. - Sem `2xx` (ou sem resposta em 15 segundos), o Couryo tenta de novo depois de 30 s, 2 min, 10 min, 30 min, 1 h, 2 h, 4 h, 8 h, 12 h, 16 h e 24 h: 12 tentativas em cerca de **3 dias**. - O painel mostra as últimas 100 tentativas de cada webhook, com o status e a resposta do seu servidor, e tem um botão para reenviar na hora. - Perdeu eventos? Liste tudo em [`GET /v1/events`](https://couryo.com/docs/events.md#listar-eventos). ## Eventos disponíveis `accepted`, `queued`, `sent`, `delivered`, `deferred`, `bounced`, `complained`, `opened`, `clicked`, `suppressed` e `failed`. O significado de cada um está em [Eventos e linha do tempo](https://couryo.com/docs/events.md). --- # Eventos e linha do tempo > Cada etapa de um e-mail no Couryo, com horário, servidor de destino, código SMTP, resposta original e explicação em linguagem simples. Fonte: https://couryo.com/docs/events Todo e-mail tem uma linha do tempo. Cada evento traz o horário, o servidor de destino, o código SMTP, **a resposta original do servidor** e uma explicação em linguagem simples, no idioma da sua conta. ## Ver a linha do tempo ```bash title="cURL" curl https://api.couryo.com/v1/emails/msg_9w2k7c1x0d4e5f6g \ -H "Authorization: Bearer $COURYO_API_KEY" ``` ```json title="Resposta" { "id": "msg_9w2k7c1x0d4e5f6g", "from": "Loja Exemplo ", "to": ["ana@exemplo.com.br"], "subject": "Seu pedido 1042 foi confirmado", "stream": "transactional", "status": "delivered", "tags": { "tipo": "pedido" }, "metadata": { "pedido_id": "1042" }, "created_at": "2026-10-07T18:30:00Z", "events": [ { "id": "evt_1a", "type": "accepted", "at": "2026-10-07T18:30:00Z" }, { "id": "evt_1b", "type": "queued", "at": "2026-10-07T18:30:00Z" }, { "id": "evt_1c", "type": "sent", "at": "2026-10-07T18:30:01Z", "recipient": "ana@exemplo.com.br" }, { "id": "evt_1d", "type": "delivered", "at": "2026-10-07T18:30:02Z", "recipient": "ana@exemplo.com.br", "mx": "gmail-smtp-in.l.google.com", "provider": "gmail", "smtp_code": "250 2.0.0", "smtp_response": "250 2.0.0 OK 1791484202 a1b2c3d4e5f6 - gsmtp", "explanation": "O Gmail aceitou a mensagem." } ] } ``` ## Tipos de evento | Evento | O que significa | |---|---| | `accepted` | a API recebeu e validou o pedido | | `queued` | o e-mail está na fila (ou aguardando o `scheduled_at`) | | `sent` | saiu do Couryo para o servidor de destino | | `delivered` | o servidor de destino aceitou a mensagem | | `deferred` | o destino pediu para tentar mais tarde (código `4xx`); o Couryo tenta de novo sozinho | | `bounced` | o destino recusou de vez (código `5xx`) | | `complained` | o destinatário marcou como spam | | `opened` | o e-mail foi aberto (só no fluxo `marketing`; impreciso, porque alguns clientes de e-mail abrem tudo sozinhos) | | `clicked` | um link foi clicado (só no fluxo `marketing`) | | `suppressed` | não enviado porque o endereço está na [lista de supressão](https://couryo.com/docs/suppressions.md) | | `failed` | não foi enviado: o conteúdo foi barrado pela checagem de entrada (phishing, link em lista de bloqueio) ou o envio falhou; o motivo vem no evento | > "Entregue" quer dizer que o servidor de destino aceitou. Se foi para a caixa de entrada ou para o spam, nenhum provedor informa por mensagem. Por isso o painel mostra a entrega separada por provedor; o acompanhamento de reputação pelo agente de entregabilidade vem em breve. ## Status do e-mail O campo `status` resume a linha do tempo: `queued`, `sent`, `delivered`, `deferred`, `bounced`, `complained`, `failed`, `suppressed` ou `captured` (chave de teste, não entregue). ## Provedores O campo `provider` agrupa o destino: `gmail`, `microsoft`, `yahoo`, `uol`, `bol`, `terra`, `locaweb` ou `other`. O painel mostra a entrega separada por provedor. ## Devoluções: permanente, temporária e bloqueio - **Permanente** (`5.1.x`, usuário ou domínio inexistente): o endereço vai na hora para a lista de supressão. - **Temporária** (`4.x.x`): o Couryo tenta de novo sozinho. Se o mesmo endereço voltar em 3 dias diferentes (numa janela de 14 dias), ele é suprimido. - **Bloqueio por política** (`5.7.x`, mensagens com "blocked" ou "spam"): o problema não é do destinatário. O endereço não é suprimido e a devolução não conta na sua taxa de devolução. ## Listar eventos `GET /v1/events` (escopo `read`) lista os eventos de todos os e-mails do projeto, do mais novo para o mais antigo, cada um com o `email_id`. Útil para quem perdeu webhooks ou prefere buscar de tempos em tempos. Filtros: `type` (por exemplo, `bounced`) e `email_id`. ```bash title="cURL" curl "https://api.couryo.com/v1/events?type=bounced&limit=50" \ -H "Authorization: Bearer $COURYO_API_KEY" ``` ```json title="Resposta" { "data": [{ "id": "evt_7d3k9s0a2m5n8b1c", "email_id": "msg_9w2k7c1x0d4e5f6g", "type": "bounced", "at": "2026-10-07T18:31:04Z", "recipient": "maria@empresa.com.br", "smtp_code": "550 5.1.1" }], "has_more": true } ``` Para a próxima página, passe `starting_after` com o `id` do último item. ## Por quanto tempo A linha do tempo fica disponível pelo prazo de logs do seu plano: 7 dias no Grátis, 30 dias no Pro e 90 dias no Escala. --- # Supressão > A lista de supressão do Couryo protege sua reputação. Motivos, fluxos, como consultar, adicionar e remover endereços pela API. Fonte: https://couryo.com/docs/suppressions A lista de supressão guarda endereços para os quais o Couryo não envia mais. Ela protege sua reputação: insistir num endereço que não existe ou em quem reclamou derruba a entrega de todos os seus e-mails. ## Motivos | `reason` | Quando entra | Vale para | |---|---|---| | `hard_bounce` | o endereço não existe (`5.1.x`) | todos os fluxos | | `complaint` | o destinatário marcou como spam | todos os fluxos | | `unsubscribe` | o destinatário se descadastrou | só o fluxo onde se descadastrou (em geral, `marketing`) | | `manual` | você adicionou | o fluxo que você escolher | Descadastro de marketing **nunca** bloqueia o transacional: quem saiu da newsletter continua recebendo o e-mail de troca de senha. ## O que acontece no envio Se **alguns** destinatários estão suprimidos, o e-mail sai para os outros e a linha do tempo ganha um evento `suppressed` para cada endereço pulado. Se **todos** estão suprimidos, o envio é recusado com `422 recipient_suppressed`, e o campo `param` indica o primeiro (`to[0]`). No envio em lote, só o item afetado é recusado. Devoluções temporárias (`4.x.x`) também levam à supressão quando o mesmo endereço volta em 3 dias diferentes. ## Consultar ```bash title="cURL" curl "https://api.couryo.com/v1/suppressions?limit=25" \ -H "Authorization: Bearer $COURYO_API_KEY" ``` ```json title="Resposta" { "data": [ { "email": "maria@empresa.com.br", "reason": "hard_bounce", "stream": "all", "created_at": "2026-10-07T18:31:04Z" } ], "has_more": false } ``` ## Adicionar ```bash title="cURL" curl https://api.couryo.com/v1/suppressions \ -H "Authorization: Bearer $COURYO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "email": "nao-enviar@exemplo.com.br", "stream": "marketing" }' ``` ## Remover ```bash title="cURL" curl -X DELETE https://api.couryo.com/v1/suppressions/maria@empresa.com.br \ -H "Authorization: Bearer $COURYO_API_KEY" ``` O endereço sai de todos os fluxos (`204`). Remova só quando tiver certeza de que o problema foi resolvido (por exemplo, a pessoa confirmou que o endereço voltou a funcionar). ## Escopos e filtros - Listar exige escopo `read`; adicionar e remover, `admin`. - A listagem aceita `q` (parte do endereço), `reason` e `limit` (padrão 100), com `starting_after` para a próxima página. - Adicionar entra sempre com o motivo `manual`; `stream` pode ser `transactional`, `marketing` ou `all` (padrão). Um endereço que já está na lista devolve `409 already_suppressed`. --- # Erros > Formato dos erros da API do Couryo, tipos, status HTTP e todos os códigos com causa e correção. Fonte: https://couryo.com/docs/errors Todo erro tem o mesmo formato. O `code` é estável: pode usar no seu código sem medo de mudar. Cada `code` tem sempre o mesmo `type` e o mesmo status HTTP. ```json title="Erro" { "error": { "type": "invalid_request", "code": "domain_not_verified", "message": "O domínio exemplo.com.br ainda não foi verificado neste projeto.", "param": "from", "doc_url": "https://couryo.com/docs/errors#domain_not_verified", "request_id": "req_6f2a9c1e7b0k3m4n" } } ``` | Campo | O que é | |---|---| | `type` | a categoria do erro (tabela abaixo) | | `code` | o motivo exato, estável | | `message` | explicação para pessoas, em português ou inglês conforme o `Accept-Language` (o padrão é português) | | `param` | o campo com problema, quando houver (por exemplo, `to[1]` ou `emails[2].from`) | | `doc_url` | o link para a explicação nesta página | | `request_id` | o ID da chamada, igual ao cabeçalho `Request-Id`; mande ao suporte se precisar de ajuda | ## Tipos | `type` | HTTP | Quando | |---|---|---| | `invalid_request` | 400 ou 422 | o pedido tem algum problema que você pode corrigir | | `authentication` | 401 | chave ausente ou inválida | | `permission` | 403 | a chave ou o plano não permite isso | | `not_found` | 404 | o recurso ou a rota não existe | | `conflict` | 409 | conflito, como uma chave de idempotência reaproveitada | | `rate_limited` | 429 | muitas chamadas em pouco tempo | | `tier_limit` | 429 | limite do seu nível, do seu plano ou do seu limite de gasto | | `api_error` | 500 ou 503 | problema do nosso lado; pode tentar de novo | ## Códigos | `code` | `type` | HTTP | Resumo | |---|---|---|---| | [`missing_field`](#missing_field) | `invalid_request` | 400 | falta um campo obrigatório | | [`invalid_field`](#invalid_field) | `invalid_request` | 400 | um campo tem valor inválido | | [`invalid_json`](#invalid_json) | `invalid_request` | 400 | o corpo não é JSON | | [`batch_too_large`](#batch_too_large) | `invalid_request` | 400 | mais de 100 e-mails no lote | | [`invalid_cloudflare_token`](#invalid_cloudflare_token) | `invalid_request` | 400 | token da Cloudflare recusado | | [`cloudflare_zone_not_found`](#cloudflare_zone_not_found) | `invalid_request` | 400 | o token não alcança a zona do domínio | | [`domain_not_verified`](#domain_not_verified) | `invalid_request` | 422 | o domínio do `from` não está verificado | | [`recipient_suppressed`](#recipient_suppressed) | `invalid_request` | 422 | todos os destinatários estão suprimidos | | [`missing_api_key`](#missing_api_key) | `authentication` | 401 | sem chave | | [`invalid_api_key`](#invalid_api_key) | `authentication` | 401 | chave inválida | | [`not_authenticated`](#not_authenticated) | `authentication` | 401 | painel sem sessão | | [`insufficient_scope`](#insufficient_scope) | `permission` | 403 | escopo da chave insuficiente | | [`ip_not_allowed`](#ip_not_allowed) | `permission` | 403 | IP fora da lista da chave | | [`sandbox_recipient`](#sandbox_recipient) | `permission` | 403 | conta no nível 0 enviando para fora | | [`domain_limit_reached`](#domain_limit_reached) | `permission` | 403 | a conta já tem os domínios do plano | | [`forbidden_origin`](#forbidden_origin) | `permission` | 403 | escrita no painel vinda de outro site | | [`resource_not_found`](#resource_not_found) | `not_found` | 404 | ID inexistente | | [`route_not_found`](#route_not_found) | `not_found` | 404 | rota inexistente | | [`idempotency_conflict`](#idempotency_conflict) | `conflict` | 409 | mesma chave de idempotência, corpo diferente | | [`idempotency_in_progress`](#idempotency_in_progress) | `conflict` | 409 | o mesmo pedido ainda está em andamento | | [`domain_exists`](#domain_exists) | `conflict` | 409 | domínio já cadastrado | | [`already_suppressed`](#already_suppressed) | `conflict` | 409 | endereço já está na lista | | [`last_project`](#last_project) | `conflict` | 409 | tentativa de apagar o único projeto | | [`rate_limited`](#rate_limited) | `rate_limited` | 429 | chamadas demais por segundo | | [`daily_limit_reached`](#daily_limit_reached) | `tier_limit` | 429 | limite diário atingido | | [`monthly_limit_reached`](#monthly_limit_reached) | `tier_limit` | 429 | cota do mês atingida | | [`spend_limit_reached`](#spend_limit_reached) | `tier_limit` | 429 | o excedente chegou ao seu limite de gasto | | [`sending_paused`](#sending_paused) | `tier_limit` | 429 | envio pausado, com motivo | | [`internal_error`](#internal_error) | `api_error` | 500 | erro do nosso lado | | [`billing_unavailable`](#billing_unavailable) | `api_error` | 503 | cobrança indisponível agora | ### missing_field Falta um campo obrigatório, indicado em `param`. Para enviar, são obrigatórios `from`, `to`, `subject` e um entre `html` ou `text` (sem nenhum dos dois, o `param` vem como `html`). ### invalid_field O campo indicado em `param` tem um valor que não aceitamos: um endereço mal formado, mais de 50 destinatários somando `to`, `cc` e `bcc`, um `scheduled_at` no passado ou a mais de 30 dias, uma URL de webhook sem `https://`. A `message` diz o que está errado. Em lote, o `param` vem com o índice: `emails[2].to[0]`. ### invalid_json O corpo não é um JSON válido. Confira o `Content-Type: application/json` e as aspas. ### batch_too_large O lote tem mais de 100 e-mails. Divida em chamadas menores. ### invalid_cloudflare_token O token da Cloudflare não foi aceito. Ele precisa ter a permissão **Zona > DNS > Editar** para o domínio. Veja [Domínios e DNS](https://couryo.com/docs/domains.md#conectar-a-cloudflare). ### cloudflare_zone_not_found O token é válido, mas não dá acesso à zona do domínio na Cloudflare. Crie o token incluindo essa zona. ### domain_not_verified O domínio do `from` não está verificado no projeto da chave. Verifique em [Domínios e DNS](https://couryo.com/docs/domains.md) e tente de novo. Chaves `ck_test_` não exigem domínio verificado. ### recipient_suppressed **Todos** os destinatários estão na [lista de supressão](https://couryo.com/docs/suppressions.md), por devolução permanente, reclamação, descadastro ou inclusão manual. O `param` indica o primeiro (`to[n]`). Se só alguns estiverem suprimidos, o e-mail sai para os outros e a linha do tempo ganha um evento `suppressed` para cada endereço pulado. ### missing_api_key Faltou o cabeçalho `Authorization: Bearer ck_...`. Veja [Autenticação](https://couryo.com/docs/authentication.md). ### invalid_api_key A chave não existe, foi apagada ou foi copiada pela metade. Crie uma nova no painel se precisar. ### not_authenticated Chamada ao painel (`/api`) sem sessão. Entre de novo em [app.couryo.com](https://app.couryo.com). Não acontece na API pública (`/v1`). ### insufficient_scope A chave não tem permissão para esta operação. Por exemplo, uma chave `send` tentando ler e-mails, que exige `read`. A `message` diz qual escopo falta. ### ip_not_allowed A chave tem uma lista de IPs permitidos, e a chamada veio de outro endereço. Ajuste a lista no painel. ### sandbox_recipient A conta está no nível 0 (sandbox), ou o remetente é `teste@sandbox.couryo.com`, e esse caminho só envia para os e-mails da própria conta. Verifique um domínio para subir ao nível 1. Veja [Limites e níveis](https://couryo.com/docs/limits.md). ### domain_limit_reached A conta já tem o número de domínios que o plano permite: Grátis 1, Pro 10, Escala 50, Empresa sem limite (somando todos os projetos). Apague um domínio que não usa ou mude de plano. A `message` diz o limite atual e o do plano seguinte. ### forbidden_origin Uma escrita no painel veio de outro site (proteção contra CSRF). Não acontece na API pública. ### resource_not_found O ID informado não existe neste projeto, ou pertence a outro projeto. A `message` diz qual recurso não foi encontrado. ### route_not_found O caminho não existe. Confira o método e o endereço, por exemplo `POST /v1/emails`. ### idempotency_conflict A mesma `Idempotency-Key` foi usada nas últimas 24 horas com um corpo diferente. Use uma chave nova para um pedido novo. Veja [Idempotência](https://couryo.com/docs/sending.md#idempotencia). ### idempotency_in_progress Um pedido com a mesma `Idempotency-Key` ainda está sendo processado. Espere o `Retry-After` (1 segundo) e repita: você recebe a resposta do primeiro. ### domain_exists Esse domínio já está cadastrado no Couryo, nesta ou em outra conta. Se ele é seu e está em outra conta, apague de lá primeiro. ### already_suppressed O endereço já está na lista de supressão desse fluxo. ### last_project A conta precisa de pelo menos um projeto. Crie outro antes de apagar este. ### rate_limited Chamadas demais em pouco tempo (o padrão é 10 por segundo por chave). Espere os segundos do cabeçalho `Retry-After` e tente de novo com a mesma `Idempotency-Key`. Os cabeçalhos `RateLimit-Limit`, `RateLimit-Remaining` e `RateLimit-Reset` mostram sua folga. ### daily_limit_reached Você atingiu o limite diário do seu nível de confiança, ou o do plano Grátis (100 por dia). A `message` diz o limite, quanto já foi enviado e o que falta para subir de nível. O contador zera à meia-noite no fuso da sua conta. ### monthly_limit_reached Você atingiu a cota do mês: 3 mil no plano Grátis e também no nível 1 de qualquer plano. O envio volta no mês seguinte (no plano pago, no próximo período). No nível 1, a cota sobe quando a conta chega ao nível 2; no Grátis, ao assinar o Pro ou o Escala. ### spend_limit_reached O excedente do mês chegou ao limite de gasto que você definiu em **Cobrança**. **Todo** o envio para (transacional e marketing) até você aumentar o limite ou o período virar. Nada é cobrado acima do teto. ### sending_paused O envio está pausado. Na pausa comum, o marketing para e o transacional continua com até 30 por dia; na pausa total, tudo para. A `message` traz o motivo, os números e como corrigir, e o painel tem o botão **Pedir revisão**. Veja [Limites e níveis](https://couryo.com/docs/limits.md#pausas). ### internal_error Um problema do nosso lado. O pedido **não** foi processado. Tente de novo com a mesma `Idempotency-Key`: não há risco de duplicar. ### billing_unavailable A cobrança está indisponível agora (checkout ou portal de pagamento). Tente de novo em alguns minutos; o envio de e-mails não é afetado. --- # Limites e níveis de confiança > Níveis de confiança do Couryo, limites por dia e por mês, limites de cada plano (e-mails e domínios), limite de gasto, cabeçalhos de limite de requisição, pausas e como pedir revisão. Fonte: https://couryo.com/docs/limits O Couryo protege a reputação de todos os clientes sem pegar ninguém de surpresa. Os limites são públicos, aparecem no painel e nas mensagens de erro da API, e toda pausa vem com motivo e números. ## Níveis de confiança | Nível | Como entra | Limite | |---|---|---| | 0, sandbox | conta criada | 25 por dia, só para os e-mails da própria conta | | 1, novo | um domínio verificado (DKIM, return path e DMARC) | 100 por dia e 3 mil por mês | | 2, verificado | 7 dias limpos e CNPJ conferido ou forma de pagamento cadastrada | 1 mil por dia, dobrando a cada semana limpa até 10 mil | | 3, confiável | 30 dias limpos no nível 2 e plano pago | o volume do seu plano, com o limite de gasto | - **Você sobe sozinho.** Não há aprovação manual nem formulário. - **O painel mostra o que falta**, por exemplo: "Faltam 4 dias limpos para o nível 2". - **"Dias limpos"** são dias sem pausa por devolução ou reclamação. - Os limites contam **destinatários** (`to` + `cc` + `bcc`), não chamadas. O dia vira à meia-noite no fuso da conta; o mês é o período pago ou, no Grátis, o mês do calendário. - Chaves `ck_test_` não contam em nenhum limite de envio. O limite de cada momento é o **menor** entre o do nível e o do plano: no plano Grátis, o teto é de 100 por dia mesmo no nível 2. ## Limites do plano | Plano | E-mails por mês | Por dia | Domínios | Logs | |---|---|---|---|---| | Grátis | 3.000 | até 100 | 1 | 7 dias | | Pro | 50 mil incluídos, excedente por mil | o do nível | até 10 | 30 dias | | Escala | 200 mil incluídos, excedente por mil | o do nível | até 50 | 90 dias | | Empresa | sob medida | sob medida | sem limite | estendido | - **Domínios** contam todos os projetos da conta. Acima do limite, a API responde `403 domain_limit_reached`; apagar um domínio libera a vaga. - **No Grátis**, o envio para na cota do mês (`monthly_limit_reached`) e volta no mês seguinte, sem cobrança. - **Nos planos pagos**, o excedente é cobrado por bloco de mil e-mails iniciado. Veja os [preços](https://couryo.com/precos). ## Limite de gasto Em **Cobrança**, você define quanto aceita pagar de excedente por mês. Quando o excedente chega a esse valor, **todo** o envio para (transacional e marketing) com `spend_limit_reached`, até você aumentar o limite ou o período virar. Nada é cobrado acima do teto. ## Limites que pausam | Medida | Pausa quando | |---|---| | Taxa de devolução | passa de 3% nas últimas 24 horas, com pelo menos 50 enviados | | Taxa de reclamação (marcado como spam) | passa de 0,05% nos últimos 7 dias, com pelo menos 200 enviados e 2 reclamações | As taxas são da conta, medidas em tempo real, e ficam bem abaixo do que os grandes provedores toleram, para corrigir cedo. Bloqueios por política do destino (`5.7.x`) não contam na taxa de devolução. ## Pausas Quando uma taxa passa do limite, a pausa é **gradual**: 1. o envio de marketing para; 2. o transacional crítico (senha, login, cobrança) continua, com até 30 por dia; 3. você recebe um e-mail com o motivo, os números e o que corrigir. Acima de 10% de devolução ou de 0,5% de reclamação, a pausa é **total**, inclusive no motor de envio, e só uma pessoa da equipe libera. Corte total também em fraude evidente, como phishing ou lista comprada. Durante a pausa, a API responde `sending_paused` com o motivo, os números e como corrigir, e o painel mostra o mesmo, com o botão **Pedir revisão**. ## Pedir revisão Discorda de uma pausa ou já corrigiu? Use o botão **Pedir revisão** no painel e explique o caso. - **Na hora:** um agente de IA analisa o pedido com os dados da conta e decide os casos claros. Aprovado, a conta fica 48 horas em observação (status `limited`), com até 50 por dia, e volta ao normal se as taxas continuarem boas. - **Em até 1 dia útil:** se a dúvida continuar, uma pessoa da equipe revisa e responde com o motivo. As regras completas estão na [política de uso aceitável e de suspensão](https://couryo.com/uso-aceitavel). ## Limite de requisições Cada chave tem uma janela de 1 segundo (o padrão é 10 chamadas por segundo). Toda resposta traz os cabeçalhos: | Cabeçalho | O que diz | |---|---| | `RateLimit-Limit` | quantas chamadas cabem na janela | | `RateLimit-Remaining` | quantas ainda restam | | `RateLimit-Reset` | em quantos segundos a janela recomeça | | `Retry-After` | em `429 rate_limited`, quantos segundos esperar | Ao receber `429`, espere o `Retry-After` e tente de novo com a mesma `Idempotency-Key`. Para mandar muitos e-mails de uma vez, use o [lote](https://couryo.com/docs/sending.md#envio-em-lote): até 100 por chamada. ## Tamanhos | O quê | Limite | |---|---| | Destinatários por e-mail (`to` + `cc` + `bcc`) | 50 | | E-mails por lote | 100 | | Anexos por e-mail | 20 | | Corpo da chamada (com anexos em Base64) | 30 MB | | `html` ou `text` | 5 MB cada | | Agendamento (`scheduled_at`) | até 30 dias à frente | --- # MCP e agentes de IA > O servidor MCP remoto do Couryo em mcp.couryo.com, as ferramentas send_email, get_email, domain_health e why_bounced, e como conectar no Claude, no Cursor e no ChatGPT. Fonte: https://couryo.com/docs/mcp O Couryo tem um servidor MCP remoto em `https://mcp.couryo.com`. Com ele, o seu agente de IA envia e-mails, consulta a linha do tempo de uma mensagem, confere o DNS de um domínio e explica por que um e-mail voltou, conversando. - **Transporte:** Streamable HTTP, sem estado. - **Entrada:** a mesma chave de API da REST, no cabeçalho `Authorization: Bearer ck_...`, com os mesmos escopos e o mesmo projeto da chave. - **OAuth:** em breve. Com ele, os conectores do Claude e do ChatGPT vão entrar sem colar chave nenhuma. ## Ferramentas | Ferramenta | Escopo | O que faz | |---|---|---| | `send_email` | `send` | envia um e-mail de um domínio verificado: `from`, `to` (texto ou lista), `subject`, `html` e/ou `text`, `stream` e `idempotency_key` opcional (24 horas, como o cabeçalho `Idempotency-Key`) | | `get_email` | `read` | traz um e-mail (`id`, `msg_...`) e a linha do tempo, com código SMTP, resposta original e explicação em linguagem simples | | `domain_health` | `read` | confere no DNS real o DKIM, o return path e o DMARC de um domínio do projeto (`domain`) e resume o que corrigir | | `why_bounced` | `read` | explica por que um e-mail (`email_id`) voltou, foi adiado ou recebeu reclamação, e o que fazer | As respostas e as mensagens de erro saem no idioma do cabeçalho `Accept-Language` (português por padrão). Os erros têm o mesmo formato e os mesmos códigos da [API](https://couryo.com/docs/errors.md). ### Qual chave usar - Uma chave `send` só usa `send_email`; uma chave `read` só usa as três de leitura. Para as quatro, use uma chave `admin`. - Comece com uma chave `ck_test_`: nada é entregue, e os [endereços simulados](https://couryo.com/docs/test-mode.md#enderecos-simulados) (`bounced@`, `complained@`, `deferred@`) deixam o agente testar o `why_bounced` de verdade. - A chave fica na configuração do cliente MCP, na sua máquina. Não cole a chave na conversa. ## Claude Code ```bash title="Terminal" claude mcp add --transport http couryo https://mcp.couryo.com \ --header "Authorization: Bearer $COURYO_API_KEY" ``` Depois, peça em linguagem natural: "por que o e-mail msg_... voltou?" ou "o DNS de exemplo.com.br está certo?". ## Claude Desktop Até o OAuth chegar, o Claude Desktop conecta pela ponte `mcp-remote`, que repassa o cabeçalho com a chave. Em **Configurações > Desenvolvedor > Editar configuração**, no `claude_desktop_config.json`: ```json title="claude_desktop_config.json" { "mcpServers": { "couryo": { "command": "npx", "args": ["-y", "mcp-remote", "https://mcp.couryo.com", "--header", "Authorization:${COURYO_AUTH}"], "env": { "COURYO_AUTH": "Bearer ck_live_..." } } } } ``` Reinicie o Claude Desktop. O conector pela tela **Configurações > Conectores** (e no claude.ai) depende do OAuth: em breve. ## Cursor Em **Settings > MCP**, ou no arquivo `.cursor/mcp.json` do projeto (`~/.cursor/mcp.json` para todos os projetos): ```json title=".cursor/mcp.json" { "mcpServers": { "couryo": { "url": "https://mcp.couryo.com", "headers": { "Authorization": "Bearer ${env:COURYO_API_KEY}" } } } } ``` Não commite a chave: use a variável de ambiente, como acima. ## ChatGPT - **App do ChatGPT (conectores):** pede OAuth. Chega junto com o OAuth do Couryo (em breve). - **API da OpenAI:** já funciona hoje, com a ferramenta de MCP remoto e o cabeçalho da chave. ```python title="Python (API da OpenAI)" import os from openai import OpenAI client = OpenAI() resp = client.responses.create( model="gpt-4.1", tools=[{ "type": "mcp", "server_label": "couryo", "server_url": "https://mcp.couryo.com", "headers": {"Authorization": f"Bearer {os.environ['COURYO_API_KEY']}"}, "require_approval": "always", }], input="Por que o e-mail msg_9w2k7c1x0d4e5f6g voltou?", ) print(resp.output_text) ``` ## Testar na mão O servidor responde JSON-RPC por `POST`. Para listar as ferramentas: ```bash title="cURL" curl https://mcp.couryo.com \ -H "Authorization: Bearer $COURYO_API_KEY" \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {} }' ``` Sem chave, ou com uma chave inválida, a resposta é `401` com o erro `missing_api_key` ou `invalid_api_key`. ## Em breve - OAuth para os conectores do Claude e do ChatGPT, sem colar chave. - Mais ferramentas: diagnóstico de entrega por provedor, configuração de DNS, supressões, templates e respostas recebidas. Toda ação que muda algo terá modo de teste e pedirá confirmação. ## Documentação para agentes - [llms.txt](https://couryo.com/llms.txt) e [llms-full.txt](https://couryo.com/llms-full.txt), em português, e as versões em inglês em [/en/llms.txt](https://couryo.com/en/llms.txt). - Toda página desta documentação em Markdown: acrescente `.md` ao endereço, como em [/docs/sending.md](https://couryo.com/docs/sending.md.md). - Preços em Markdown, em [/precos.md](https://couryo.com/precos.md) e [/pricing.md](https://couryo.com/pricing.md). Para o seu agente de código escrever a integração, aponte para o `llms-full.txt`: ```text title="Prompt" Leia https://couryo.com/llms-full.txt e integre o envio de e-mail de confirmação de pedido com a API do Couryo, usando Idempotency-Key e verificando a assinatura dos webhooks. ``` --- # Couryo: planos e preços > API de e-mail transacional e de produto. Preço por volume de e-mails, sem cobrança por usuário. Valores mensais. Página: https://couryo.com/precos Criar conta: https://app.couryo.com ## Planos | Plano | Preço no Brasil (BRL) | Preço fora do Brasil (USD) | E-mails por mês incluídos | Excedente por mil e-mails | |---|---|---|---|---| | Grátis | R$ 0 | $0 | 3.000 | não há (envio para no limite) | | Pro | R$ 99 | $19 | 50.000 | R$ 1,80 / $0.35 | | Escala | R$ 299 | $59 | 200.000 | R$ 1,40 / $0.28 | | Empresa | sob medida | sob medida | sob medida | sob medida | ### Grátis Para testar e para projetos pequenos. Para sempre. - 3.000 e-mails por mês (até 100 por dia) - 1 domínio - 7 dias de logs - API REST e webhooks (SMTP em breve) - MCP para agentes de IA - Cada devolução explicada em linguagem simples - Permanente e escrito nos termos de uso ### Pro Para produtos em produção. - 50.000 e-mails por mês incluídos - Até 10 domínios - 30 dias de logs - Usuários ilimitados, sem cobrança por usuário - Limite de gasto para o excedente - Inbound: receber e-mails por webhook (em breve) - Suporte humano em português ### Escala Para volume alto e equipes maiores. - 200.000 e-mails por mês incluídos - Tudo do Pro - Até 50 domínios - 90 dias de logs - Sequências por evento (em breve) - Prioridade no suporte ### Empresa Para quem precisa de contrato e garantias. - Volume sob medida - Domínios ilimitados - IP dedicado - SSO - SLA em contrato - Retenção de logs estendida - DPA dedicado ## Adicionais - IP dedicado - Retenção de logs extra ## Compromissos de preço - **Sem cobrança por usuário.** Chame o time inteiro. O preço depende só do volume de e-mails. - **Preço mantido por 12 meses.** Se o preço mudar, quem já é cliente mantém o valor atual por 12 meses, com 6 meses de aviso. - **Grátis para sempre, por escrito.** Os 3.000 e-mails por mês do plano Grátis estão nos termos de uso, não numa promoção. - **Cancelar em um clique.** Sem fidelidade e sem ligação de retenção. No plano anual, o reembolso é proporcional. - **Limite de gasto.** Você define quanto aceita pagar de excedente. Chegou no teto, o envio para e nada é cobrado acima dele. - **Atraso curto não derruba o crítico.** Senha, login e cobrança continuam saindo enquanto a fatura é resolvida. Dá para pagar na hora por Pix. ## Formas de pagamento - Cartão de crédito nacional, recorrente e sem IOF - Boleto recorrente, para empresas que não usam cartão - Pix para pagamentos avulsos: pagar fatura em aberto ou reativar na hora - Nota fiscal de serviço (NFS-e) emitida automaticamente a cada pagamento - Clientes de fora do Brasil pagam em dólar, com cartão internacional ## Como estimar Escolha o plano cujo preço mais o excedente fique menor para o seu volume mensal. O excedente é cobrado por bloco de mil e-mails iniciado e respeita o limite de gasto da conta. Acima de 1 milhão de e-mails por mês, fale com a gente.