correo

API HTTP · v1

Documentação para enviar com controle.

Contratos reais da API, exemplos copiáveis e os cuidados necessários para autenticar, enfileirar e acompanhar cada email.

Base URL

https://usecorreo.com/api
Sumário desta página

01 · Quickstart

Do painel ao primeiro POST

Você precisa de uma conta, uma chave de API e um domínio verificado. O envio é feito por HTTP; nenhum SDK é necessário.

  1. 1

    Crie uma chave

    No painel, abra API Keys. Copie a chave quando ela aparecer.

  2. 2

    Verifique o domínio

    Cadastre o domínio remetente e publique os registros DNS mostrados pelo painel.

  3. 3

    Envie por HTTP

    Troque a chave, o remetente e o destinatário no exemplo abaixo.

bash
curl -X POST https://usecorreo.com/api/v1/emails \
  -H "Authorization: Bearer uc_live_sua_chave" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Sua Loja <oi@sualoja.com.br>",
    "to": ["cliente@exemplo.com.br"],
    "subject": "Pedido confirmado",
    "html": "<p>Seu pedido saiu para entrega.</p>"
  }'
json
{
  "id": "eml_seu_id"
}

02 · Segurança

Autenticação Bearer

Toda chamada da API v1 exige uma chave no header Authorization.

http
Authorization: Bearer uc_live_sua_chave

A chave em claro aparece uma única vez quando é criada. Guarde-a em uma variável de ambiente ou secret manager; não envie para o navegador nem grave no repositório.

03 · Endpoint principal

Enviar um email

O contrato aceita texto, HTML, cópias, headers e anexos. O remetente precisa pertencer a um domínio verificado na conta.

POST/api/v1/emails
typescript
const response = await fetch("https://usecorreo.com/api/v1/emails", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CORREO_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    from: "Sua Loja <oi@sualoja.com.br>",
    to: ["cliente@exemplo.com.br"],
    subject: "Pedido confirmado",
    html: "<p>Seu pedido saiu para entrega.</p>",
  }),
});

const data = await response.json();
if (!response.ok) {
  throw new Error(`${data.error.code}: ${data.error.message}`);
}

console.log(data.id); // eml_...
CampoTipoRegra
fromstringObrigatório. Endereço de um domínio verificado na conta.
tostring | string[]Obrigatório. Uma lista aceita até 50 endereços.
subjectstringObrigatório. Entre 1 e 998 caracteres.
htmlstringHTML da mensagem. Informe html, text ou os dois.
textstringVersão em texto. Informe html, text ou os dois.
cc / bcc / reply_tostring | string[]Opcionais; cada lista aceita até 50 endereços.
headersRecord<string, string>Cabeçalhos adicionais da mensagem.
unsubscribebooleanForça ou desativa List-Unsubscribe neste envio.
attachmentsAttachment[]Até 20 arquivos em base64 e 10 MB no total.

Quando algum destinatário já está suprimido, a resposta também pode incluir suppressed_recipients. Se todos estiverem suprimidos, o email é registrado com status suppressed e não entra na fila.

04 · Retentativas seguras

Idempotência

Use uma chave estável por operação para impedir que o mesmo retry registre dois emails.

POST/api/v1/emails · Idempotency-Key
bash
curl -X POST https://usecorreo.com/api/v1/emails \
  -H "Authorization: Bearer uc_live_sua_chave" \
  -H "Idempotency-Key: pedido-8123-confirmacao" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Sua Loja <oi@sualoja.com.br>",
    "to": ["cliente@exemplo.com.br"],
    "subject": "Pedido confirmado",
    "text": "Seu pedido saiu para entrega."
  }'

05 · Observabilidade

Consultar um envio

Busque o estado atual e a sequência de eventos armazenados para um email da sua conta.

GET/api/v1/emails/{id}
bash
curl https://usecorreo.com/api/v1/emails/eml_seu_id \
  -H "Authorization: Bearer uc_live_sua_chave"
typescript
type EmailResponse = {
  object: "email";
  id: string;
  from: string;
  to: string[];
  cc: string[];
  bcc: string[];
  reply_to: string[];
  subject: string;
  status:
    | "queued"
    | "sending"
    | "sent"
    | "delivered"
    | "bounced"
    | "complained"
    | "failed"
    | "suppressed";
  created_at: string;
  sent_at: string | null;
  last_error: string | null;
  events: Array<{ type: string; created_at: string; payload: unknown }>;
};

O endpoint aplica ownership pela API key: um id de outra conta responde como recurso não encontrado. Eventos só aparecem quando são registrados pelo ciclo de envio.

06 · Identidade de envio

Domínios

Cadastre o domínio, publique os registros retornados e solicite uma nova verificação depois da propagação DNS.

POST/api/v1/domains
POST/api/v1/domains/{id}/verify
GET/api/v1/domains/{id}
DELETE/api/v1/domains/{id}
bash
curl -X POST https://usecorreo.com/api/v1/domains \
  -H "Authorization: Bearer uc_live_sua_chave" \
  -H "Content-Type: application/json" \
  -d '{ "name": "sualoja.com.br" }'

A criação retorna o domínio e os registros DNS em records. Depois de publicá-los, use o id retornado para verificar:

bash
curl -X POST https://usecorreo.com/api/v1/domains/dom_seu_id/verify \
  -H "Authorization: Bearer uc_live_sua_chave"

07 · Conteúdo

Anexos em base64

Envie até 20 anexos. A soma estimada do conteúdo decodificado não pode ultrapassar 10 MB.

json
{
  "from": "Sua Loja <oi@sualoja.com.br>",
  "to": ["cliente@exemplo.com.br"],
  "subject": "Seu comprovante",
  "text": "O comprovante está anexado.",
  "attachments": [
    {
      "filename": "comprovante.pdf",
      "content": "JVBERi0xLjQK...",
      "content_type": "application/pdf"
    }
  ]
}

Cada item exige filename e content. O campo content_type é opcional.

08 · Eventos

Webhooks assinados

Cadastre uma URL no painel e selecione os eventos que sua aplicação precisa receber.

Eventos disponíveis: email.sent, email.delivered, email.bounced, email.complained, email.opened, email.clicked, email.failed e email.suppressed.

O header correo-signature usa o formato t=timestamp,v1=assinatura. A assinatura é HMAC SHA-256 de timestamp.rawBody.

typescript
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyCorreoWebhook(
  signatureHeader: string,
  rawBody: string,
  secret: string,
) {
  const parts = Object.fromEntries(
    signatureHeader.split(",").map((part) => part.split("=", 2)),
  );
  const timestamp = parts.t;
  const signature = parts.v1;

  if (!timestamp || !signature) return false;
  const timestampNumber = Number(timestamp);
  if (!Number.isFinite(timestampNumber)) return false;
  if (Math.abs(Date.now() / 1000 - timestampNumber) > 300) return false;

  const expectedHex = createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");
  const expected = Buffer.from(expectedHex, "hex");
  const received = Buffer.from(signature, "hex");

  if (expected.length !== received.length) return false;
  return timingSafeEqual(expected, received);
}

A existência de um tipo na lista não garante que todo envio produzirá esse evento; o webhook é criado quando o evento correspondente é registrado.

09 · Proteção de reputação

Supressões e descadastro

Consulte, adicione ou remova endereços da lista da sua conta pela API.

GET/api/v1/suppressions
POST/api/v1/suppressions
DELETE/api/v1/suppressions/{id}
bash
# Listar — use ?email= para filtrar um endereço
curl "https://usecorreo.com/api/v1/suppressions?email=cliente@exemplo.com.br" \
  -H "Authorization: Bearer uc_live_sua_chave"

# Adicionar manualmente
curl -X POST https://usecorreo.com/api/v1/suppressions \
  -H "Authorization: Bearer uc_live_sua_chave" \
  -H "Content-Type: application/json" \
  -d '{ "email": "cliente@exemplo.com.br" }'

# Remover pelo id
curl -X DELETE https://usecorreo.com/api/v1/suppressions/sup_seu_id \
  -H "Authorization: Bearer uc_live_sua_chave"

A listagem retorna até 100 registros e aceita o filtro ?email=. Bounces permanentes, reclamações e descadastros processados pelo Correo também podem adicionar endereços à lista.

List-Unsubscribe

Contas novas habilitam os cabeçalhos de descadastro por padrão. Para desativar o comportamento em uma mensagem específica, envie unsubscribe: false:

json
{
  "from": "Sua Loja <oi@sualoja.com.br>",
  "to": ["cliente@exemplo.com.br"],
  "subject": "Seu código de acesso",
  "text": "Código: 123456",
  "unsubscribe": false
}

unsubscribe: true força a injeção. Se você já enviar um header List-Unsubscribe, o Correo preserva o valor informado.

10 · Referência

Erros estáveis em JSON

Respostas de erro usam sempre o objeto error com code e message.

json
{
  "error": {
    "code": "validation_error",
    "message": "to: Invalid email address"
  }
}
HTTPcodeQuando acontece
401unauthorizedChave ausente, inválida ou revogada.
403domain_not_verifiedO domínio do remetente não está verificado na conta.
403account_pausedA conta está pausada e não aceita novos envios.
404not_foundO recurso não existe ou não pertence à conta.
409conflictO domínio ou endereço suprimido já foi cadastrado.
422validation_errorO corpo não atende ao schema; a mensagem identifica o campo.
429rate_limitedMais de 10 requisições por segundo na mesma chave.
429quota_exceededA franquia mensal da conta foi atingida.
429daily_limit_exceededO limite diário atual foi atingido.
500internal_errorFalha interna não tratada.

11 · Compatibilidade

Migrar por HTTP

A API aceita os campos principais do payload usado pelo Resend. Faça a migração no nível HTTP: endpoint, chave e tratamento de resposta.

typescript
type EmailPayload = {
  from: string;
  to: string | string[];
  subject: string;
  html?: string;
  text?: string;
};

export async function sendWithCorreo(payload: EmailPayload) {
  const response = await fetch("https://usecorreo.com/api/v1/emails", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.CORREO_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(payload),
  });

  const data = await response.json();
  if (!response.ok) {
    throw new Error(`${data.error.code}: ${data.error.message}`);
  }
  return data as { id: string; suppressed_recipients?: string[] };
}

Revise também o retorno de erros e adicione Idempotency-Key nos fluxos que podem repetir a mesma operação.