Entrar Criar conta
Guias

Receber e-mails (Inbound)

Receba e-mails no seu app: ative o recebimento no domínio, crie endereços ou um catch-all, trate o webhook email.received e busque o conteúdo, os anexos e o original pela API.

#Como funciona

Inbound é receber e processar e-mails no seu próprio sistema: um endereço seu recebe a mensagem, a Postal guarda o texto, o HTML, os anexos e o original, avisa o seu código pelo webhook email.received e entrega o conteúdo pela API. Não é preciso manter um servidor de e-mail. Conheça a página do Inbound.

  1. Quem escreve manda a mensagem para um endereço seu (no seu domínio, com o MX apontado para mx.postal.so, ou num endereço gerado em in.postal.so).
  2. O servidor de entrada da Postal aceita a mensagem só se existe uma inbox para aquele endereço.
  3. A Postal guarda a mensagem e os anexos e cria um received_email, um por inbox de destino.
  4. Cada webhook do time que assina email.received recebe um POST assinado com o resumo.
  5. O seu código busca o conteúdo com GET /emails/receiving/{id} e faz o que precisa: abre um chamado, guarda um anexo, responde.

Inbound e Inboxes

É o mesmo recurso. As mensagens que o seu código processa também aparecem em Inboxes, no painel, organizadas em conversas. Use o Inbound quando quem trabalha é o seu código, e as Inboxes quando pessoas do time leem e respondem. Os dois funcionam ao mesmo tempo.

#Planos e disponibilidade

O plano define quantas inboxes o time pode ter e se elas podem usar um domínio seu. No plano Grátis, até 3 inboxes, todas com endereço em in.postal.so. Endereços no seu domínio, inclusive o catch-all, e a ativação do recebimento no domínio são dos planos pagos (custom_inbound_domains).

GrátisProfissionalEscalaEmpresarial
Inboxes310100Sem limite
Inboxes no seu domínioNãoSimSimSim

Receber não usa a cota de envios do plano. Respostas e encaminhamentos saem como envios normais e contam na cota. Passar do número de inboxes responde 403 plan_limit_reached; veja Limites do plano e Recursos por plano.

#Receber no seu domínio

Ative o recebimento no domínio no painel (Domínios, aba de configuração, Recebimento (inboxes)) ou pela API, com capabilities.receiving em PATCH /domains/{id} (ou ao cadastrar, em POST /domains):

PATCH /domains/{id}

cURL

curl -X PATCH https://api.postal.so/domains/0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f \
  -H "Authorization: Bearer $POSTAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "capabilities": {
      "receiving": "enabled"
    }
  }'

Node.js

const response = await fetch('https://api.postal.so/domains/0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    capabilities: {
      receiving: 'enabled',
    },
  }),
});

const data = await response.json();

Python

import os
import requests

response = requests.patch(
    "https://api.postal.so/domains/0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f",
    headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
    json={
        "capabilities": {
            "receiving": "enabled",
        },
    },
    timeout=10,
)

data = response.json()

PHP

$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);

$response = $client->patch('domains/0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f', [
    'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
    'json' => [
        'capabilities' => [
            'receiving' => 'enabled',
        ],
    ],
]);

$data = json_decode($response->getBody(), true);

Depois, crie no DNS do domínio o registro que a Postal pede:

TipoNomeValorPrioridade
MX@mx.postal.so10

O nome @ é o próprio domínio cadastrado: para respostas.lojaexemplo.com.br, o MX fica em respostas.lojaexemplo.com.br. O recebimento não depende da verificação de envio (DKIM e SPF): as mensagens chegam assim que o MX aponta para a Postal. Um domínio pode até ser só de recebimento, com capabilities.sending em disabled. Responder e encaminhar a partir do domínio, por outro lado, pedem o domínio verificado para envio.

Use um subdomínio se o domínio já recebe e-mails

O MX decide para onde vão todos os e-mails do domínio. Se lojaexemplo.com.br já recebe no Google Workspace ou no Microsoft 365, trocar o MX tira as mensagens de lá. Cadastre um subdomínio, como respostas.lojaexemplo.com.br, e ative o recebimento nele. Para saber se o domínio já tem MX de outro provedor, consulte GET /domains/{id}/receiving-check: ele só informa, não bloqueia, e o painel usa a resposta para avisar antes de ativar.

Mais detalhes sobre o DNS em Domínios e DNS.

#Endereços e catch-all

Cada endereço que recebe é uma inbox. Crie no painel, em Inboxes, ou com POST /inboxes. Há três tipos:

TipoComo criarExemplo
Endereço exatodomain_id e local_part[email protected]
Catch-alldomain_id e local_part igual a **@respostas.lojaexemplo.com.br
Endereço geradoSem domain_id: a Postal gera o endereço (inclusive no plano Grátis)[email protected]
Criar um catch-all

cURL

curl -X POST https://api.postal.so/inboxes \
  -H "Authorization: Bearer $POSTAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Respostas",
    "domain_id": "0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f",
    "local_part": "*"
  }'

Node.js

const response = await fetch('https://api.postal.so/inboxes', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'Respostas',
    domain_id: '0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f',
    local_part: '*',
  }),
});

const data = await response.json();

Python

import os
import requests

response = requests.post(
    "https://api.postal.so/inboxes",
    headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
    json={
        "name": "Respostas",
        "domain_id": "0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f",
        "local_part": "*",
    },
    timeout=10,
)

data = response.json()

PHP

$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);

$response = $client->post('inboxes', [
    'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
    'json' => [
        'name' => 'Respostas',
        'domain_id' => '0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f',
        'local_part' => '*',
    ],
]);

$data = json_decode($response->getBody(), true);

Quando uma mensagem chega, a Postal procura a inbox nesta ordem: o endereço de recebimento em in.postal.so de uma inbox (receiving_address), o endereço exato e, por último, o catch-all do domínio. O endereço exato vence o catch-all. Endereços são comparados sem diferença entre maiúsculas e minúsculas e são únicos em toda a Postal.

  • Sem tratamento especial para +: suporte+pedido@ só chega se existir uma inbox com esse endereço exato ou um catch-all no domínio. Para um endereço por pedido ou por conversa, use o catch-all.
  • Encaminhar de outro provedor, sem trocar o MX: cada inbox de um endereço exato no seu domínio ganha também um receiving_address em in.postal.so. Configure o seu provedor atual para encaminhar para ele e as mensagens chegam à inbox. O catch-all não tem esse endereço.
  • Uma mensagem para duas inboxes vira dois e-mails recebidos e dois eventos, um por inbox. Duas vezes a mesma inbox (por exemplo, em to e em cc) dá um só.

#O webhook email.received

Cadastre a URL do seu sistema em Webhooks, no painel, e marque Recebido, ou crie com POST /webhooks. A URL precisa ser HTTPS e pública. Guarde o signing_secret (whsec_…) da resposta: ele confere a assinatura.

POST /webhooks

cURL

curl -X POST https://api.postal.so/webhooks \
  -H "Authorization: Bearer $POSTAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "endpoint": "https://lojaexemplo.com.br/webhooks/postal",
    "events": ["email.received"]
  }'

Node.js

const response = await fetch('https://api.postal.so/webhooks', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    endpoint: 'https://lojaexemplo.com.br/webhooks/postal',
    events: ['email.received'],
  }),
});

const data = await response.json();

Python

import os
import requests

response = requests.post(
    "https://api.postal.so/webhooks",
    headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
    json={
        "endpoint": "https://lojaexemplo.com.br/webhooks/postal",
        "events": ["email.received"],
    },
    timeout=10,
)

data = response.json()

PHP

$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);

$response = $client->post('webhooks', [
    'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
    'json' => [
        'endpoint' => 'https://lojaexemplo.com.br/webhooks/postal',
        'events' => ['email.received'],
    ],
]);

$data = json_decode($response->getBody(), true);

Assim que guarda a mensagem, a Postal envia um POST com este corpo:

email.received
{
  "type": "email.received",
  "created_at": "2026-11-04T12:41:09.880Z",
  "data": {
    "email_id": "019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d",
    "inbox_id": "0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
    "thread_id": "019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e",
    "created_at": "2026-11-04T12:41:09.000Z",
    "from": "Bruno Lima <[email protected]>",
    "to": ["[email protected]"],
    "cc": [],
    "subject": "Nota fiscal do pedido #10475",
    "message_id": "[email protected]",
    "attachments": [
      {
        "id": "019a1c00-3e50-7b6c-8d7e-9f0a1b2c3d4e",
        "filename": "comprovante-10475.pdf",
        "content_type": "application/pdf",
        "size": 48213
      }
    ]
  }
}
Campo de dataO que é
email_idID do e-mail recebido. Use em GET /emails/receiving/{id}.
inbox_idA inbox que recebeu. Um webhook vale para todas as inboxes do time: filtre por este campo para tratar só uma.
thread_idA conversa: mensagens que respondem umas às outras (pelos cabeçalhos In-Reply-To e References) têm o mesmo.
created_atQuando a mensagem foi recebida (ISO 8601, UTC).
fromO remetente, como veio no cabeçalho From (com o nome, se houver).
to, ccOs destinatários dos cabeçalhos To e Cc. Uma inbox em cópia oculta não aparece aqui: use o inbox_id.
subjectO assunto.
message_idO Message-ID da mensagem, sem os sinais < >, ou null.
attachmentsid, filename (pode ser null), content_type e size (bytes) de cada anexo. Sem o conteúdo.

O evento não traz o corpo nem o otp_token: um webhook passa por filas, logs e novas tentativas, e o conteúdo fica atrás da sua chave de API. Responda com 2xx em até 15 segundos; senão, a Postal tenta de novo até 8 vezes em cerca de 27 horas, sempre com o mesmo webhook-id (use-o para descartar repetições). Veja Respostas e novas tentativas e o histórico de entregas.

#Verificar a assinatura e ler o e-mail

Cada entrega traz os cabeçalhos webhook-id, webhook-timestamp e webhook-signature (padrão Standard Webhooks, HMAC-SHA256 com o signing_secret; os mesmos valores vêm em svix-*). Os SDKs conferem a assinatura, recusam mensagens com mais de 5 minutos de diferença e devolvem o evento. O exemplo abaixo confere, responde logo e depois busca o conteúdo e os anexos:

Webhook de e-mails recebidos

Node.js

import express from 'express';
import { verifyWebhook, PostalWebhookVerificationError } from 'postal-so';

const app = express();
const api = { Authorization: `Bearer ${process.env.POSTAL_API_KEY}` }; // chave de acesso total

// O corpo precisa chegar cru (Buffer) para a assinatura bater.
app.post('/webhooks/postal', express.raw({ type: 'application/json' }), async (req, res) => {
  let event;

  try {
    event = verifyWebhook(req.body, req.headers, process.env.POSTAL_WEBHOOK_SECRET);
  } catch (error) {
    if (error instanceof PostalWebhookVerificationError) return res.sendStatus(400);
    throw error;
  }

  // Responda logo (até 15 s) e processe numa fila; o webhook-id não muda nas novas tentativas.
  res.sendStatus(200);

  if (event.type !== 'email.received') return;

  const { email_id, inbox_id, from, subject } = event.data;
  const email = await fetch(`https://api.postal.so/emails/receiving/${email_id}`, { headers: api })
    .then((response) => response.json());

  for (const attachment of email.attachments) {
    const file = await fetch(attachment.download_url); // link assinado, vale 1 hora
    await salvarAnexo(attachment.filename ?? attachment.id, Buffer.from(await file.arrayBuffer()));
  }

  await abrirChamado({ inbox_id, from, subject, text: email.text, html: email.html });
});

PHP

use Postal\Exceptions\WebhookVerificationException;
use Postal\Webhook;

try {
    $event = Webhook::verify(
        file_get_contents('php://input'), // corpo cru
        getallheaders(),
        getenv('POSTAL_WEBHOOK_SECRET'),
    );
} catch (WebhookVerificationException $e) {
    http_response_code(400);
    exit;
}

if ($event['type'] === 'email.received') {
    $id = $event['data']['email_id'];
    $api = stream_context_create(['http' => [
        'header' => 'Authorization: Bearer ' . getenv('POSTAL_API_KEY'), // chave de acesso total
    ]]);
    $email = json_decode(file_get_contents("https://api.postal.so/emails/receiving/{$id}", false, $api), true);

    foreach ($email['attachments'] as $attachment) {
        $name = basename($attachment['filename'] ?? $attachment['id']);
        file_put_contents("/var/anexos/{$id}-{$name}", file_get_contents($attachment['download_url']));
    }

    abrirChamado($event['data']['from'], $event['data']['subject'], $email['text'], $email['html']);
}

http_response_code(200);

Instale com npm install postal-so ou composer require postal-so/postal-php (veja SDKs). Sem SDK, ou em outra linguagem, confira a assinatura como em Verificar a assinatura, sempre sobre o corpo exatamente como chegou.

#Conteúdo, anexos e original

GET /emails/receiving/{id} devolve a mensagem completa: text, html, reply_to, os cabeçalhos guardados e os anexos. É preciso uma chave de API de acesso total; uma chave só de envio recebe 401 restricted_api_key.

GET /emails/receiving/{id}

cURL

curl https://api.postal.so/emails/receiving/019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d \
  -H "Authorization: Bearer $POSTAL_API_KEY"

Node.js

const response = await fetch('https://api.postal.so/emails/receiving/019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d', {
  headers: {
    Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
  },
});

const data = await response.json();

Python

import os
import requests

response = requests.get(
    "https://api.postal.so/emails/receiving/019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d",
    headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
    timeout=10,
)

data = response.json()

PHP

$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);

$response = $client->get('emails/receiving/019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d', [
    'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);

$data = json_decode($response->getBody(), true);
200Resposta
{
  "object": "received_email",
  "id": "019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d",
  "inbox_id": "0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
  "created_at": "2026-11-04T12:41:09.000000Z",
  "from": "Bruno Lima <[email protected]>",
  "to": ["[email protected]"],
  "cc": [],
  "subject": "Nota fiscal do pedido #10475",
  "message_id": "[email protected]",
  "read": false,
  "size": 66103,
  "has_attachments": true,
  "otp_token": null,
  "reply_to": [],
  "html": "<p>Oi! Pode reenviar a nota fiscal do pedido #10475? Segue o comprovante.</p>",
  "text": "Oi! Pode reenviar a nota fiscal do pedido #10475? Segue o comprovante.",
  "headers": {
    "message-id": ["<[email protected]>"],
    "date": ["Wed, 4 Nov 2026 09:41:07 -0300"]
  },
  "attachments": [
    {
      "id": "019a1c00-3e50-7b6c-8d7e-9f0a1b2c3d4e",
      "filename": "comprovante-10475.pdf",
      "content_type": "application/pdf",
      "content_id": null,
      "size": 48213,
      "download_url": "https://api.postal.so/received-attachments/019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d/019a1c00-3e50-7b6c-8d7e-9f0a1b2c3d4e?expires=1762263669&signature=5f1c…"
    }
  ]
}
  • Anexos: cada um tem download_url, um link assinado que vale 1 hora e não pede a chave. Passou do prazo, o link responde 403: busque a mensagem de novo para um link novo. Imagens embutidas no HTML têm content_id.
  • Cabeçalhos: headers guarda, em listas, message-id, in-reply-to, references, date, list-unsubscribe, auto-submitted, x-postal-forwarded e otp-token, quando a mensagem os tem. Os demais estão no original.
  • Original: GET /emails/receiving/{id}/raw devolve a mensagem exatamente como chegou ao servidor (text/plain, RFC 5322), com todos os cabeçalhos e partes MIME.
  • Códigos de acesso: otp_token é {"code", "origin"} quando a mensagem tem um cabeçalho OTP-Token válido, e null nos outros casos. Veja Cabeçalho OTP-Token, com um teste de login de ponta a ponta.
  • Lidas e não lidas: marque com PATCH /emails/receiving/{id} ({"read": true}) para controlar o que o seu código já processou.

Sem webhook: consultar a lista

Se o seu sistema não recebe webhooks, consulte GET /emails/receiving de tempos em tempos, filtrando pela inbox e pelas não lidas, e marque como lida cada mensagem processada. A lista traz o resumo, da mais recente para a mais antiga.

GET /emails/receiving

cURL

curl 'https://api.postal.so/emails/receiving?inbox_id=0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f&unread=true&limit=20' \
  -H "Authorization: Bearer $POSTAL_API_KEY"

Node.js

const response = await fetch('https://api.postal.so/emails/receiving?inbox_id=0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f&unread=true&limit=20', {
  headers: {
    Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
  },
});

const data = await response.json();

Python

import os
import requests

response = requests.get(
    "https://api.postal.so/emails/receiving",
    headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
    params={
        "inbox_id": "0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
        "unread": "true",
        "limit": "20",
    },
    timeout=10,
)

data = response.json()

PHP

$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);

$response = $client->get('emails/receiving', [
    'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
    'query' => [
        'inbox_id' => '0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f',
        'unread' => 'true',
        'limit' => '20',
    ],
]);

$data = json_decode($response->getBody(), true);

#Responder e encaminhar

Para responder pelo seu sistema, envie com POST /emails, de um domínio verificado, e passe os cabeçalhos In-Reply-To e References com o message_id recebido entre < >. Assim o cliente de e-mail de quem escreveu mostra a resposta na mesma conversa. Use o reply_to da mensagem, quando houver, como destinatário.

Responder (Node.js)
// email: a resposta de GET /emails/receiving/{id}
const original = `<${email.message_id}>`; // message_id vem sem os sinais < >

await fetch('https://api.postal.so/emails', {
  method: 'POST',
  headers: { ...api, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    from: 'Loja Exemplo <[email protected]>',
    to: email.reply_to.length ? email.reply_to : [email.from],
    subject: email.subject.startsWith('Re:') ? email.subject : `Re: ${email.subject}`,
    text: 'Oi, Bruno! Segue a nota fiscal do pedido #10475.',
    headers: { 'In-Reply-To': original, References: original },
  }),
});
  • Pela conversa da inbox: numa inbox de um domínio seu verificado, POST /inboxes/{id}/threads/{thread_id}/reply responde e guarda a resposta na conversa, visível no painel. Inboxes em in.postal.so só recebem.
  • Encaminhar automaticamente: forward_to na inbox (até 5 endereços, em PATCH /inboxes/{id}) encaminha cada mensagem nova. Só em inboxes de endereço exato num domínio seu verificado.
  • Encaminhar uma mensagem: POST /emails/receiving/{id}/forward, para até 5 endereços, com as mesmas condições.

#Limites e retenção

ItemLimiteO que acontece
Tamanho da mensagem25 MB, com os anexosO servidor recusa na entrega: 552 5.3.4.
Destinatários por entrega50Os seguintes recebem 452 4.5.3, e o servidor remetente os entrega numa próxima conexão.
Mensagens por servidor remetente60 por minuto, por IPAviso temporário 421 4.7.0; o servidor remetente tenta de novo.
Webhook2xx em até 15 segundosAté 8 tentativas em cerca de 27 horas, com o mesmo webhook-id.
Link de anexo1 horaBusque a mensagem de novo para um link novo.
Encaminhamento5 endereçosSó em inboxes de endereço exato num domínio verificado.
RetençãoO prazo do plano (retention_days), 30 dias por padrãoA mensagem, os anexos e o original são apagados na limpeza da noite.

O que a Postal não faz com o que chega

A Postal não filtra spam nem vírus e não verifica nem registra SPF, DKIM ou DMARC das mensagens recebidas. O seu código recebe tudo o que foi enviado a um endereço que tem inbox. Se o seu fluxo depende de quem enviou, confira no original (/raw), trate anexos como arquivos não confiáveis e guarde no seu sistema o que precisar manter além da retenção.

#Erros e solução de problemas

O que aconteceCausa provável e o que fazer
Quem envia recebe 550 5.1.1 Recipient address rejected: no such mailboxNão há inbox para o endereço, ou o domínio está com o recebimento desligado. Crie a inbox (ou um catch-all) e confira capabilities.receiving.
As mensagens continuam chegando no Google ou no MicrosoftO domínio ainda tem outro MX, ou o novo registro não propagou. Consulte GET /domains/{id}/receiving-check e prefira um subdomínio.
552 5.3.4 ou 421 4.7.0 na entregaMensagem acima de 25 MB, ou mais de 60 mensagens por minuto do mesmo servidor. O segundo caso é temporário.
451 4.3.0 Temporary failure na entregaFalha temporária ao guardar a mensagem. O servidor remetente tenta de novo sozinho.
403 plan_limit_reached ao criar a inbox ou ativar o recebimentoO plano não inclui inboxes no seu domínio, ou o time chegou ao número de inboxes do plano.
422 validation_error ao criar a inboxO domínio não tem o recebimento ativo, o local_part é inválido ou o endereço já está em uso.
O webhook não chegaConfira se o endpoint assina email.received e está ativo: um endpoint que falha por 5 dias é desativado. As tentativas ficam em Webhooks (GET /webhooks/{id}/events), com o status e a resposta de cada uma.
A assinatura não confereO corpo foi lido como JSON antes da verificação, o segredo é de outro endpoint ou o relógio do servidor está fora de hora (mais de 5 minutos).
401 restricted_api_key ao ler a mensagemA chave é só de envio. Os endpoints de recebidos pedem uma chave de acesso total.
403 no download_urlO link passou de 1 hora. Busque a mensagem de novo.
404 ao buscar a mensagemA mensagem foi excluída ou passou da retenção, ou a chave é de outro time.

Veja também Erros, a referência de Recebidos e inboxes e o guia de Webhooks.