Recebidos e inboxes
Inboxes para receber e-mails e os endpoints para ler, encaminhar e organizar as mensagens recebidas.
Receba e-mails na Postal: crie inboxes num domínio seu (com recebimento ativo) ou num endereço gerado em in.postal.so, leia as mensagens pela API e seja avisado na hora pelo webhook email.received. Veja como apontar o MX em Domínios e DNS. A Postal aceita mensagens de até 25 MB. Os endpoints desta página exigem uma chave de acesso total, e o plano define quantas inboxes, e quais recursos, o time tem (veja Limites do plano).
#Listar inboxes
GET/inboxes
Lista as inboxes do time com a contagem de mensagens não lidas. Aceita limit, after e before.
Parâmetros de consulta
limitintegerDe 1 a 100. Padrão: 20.afterstringCursor da próxima página.beforestringCursor da página anterior.
cURL
curl https://api.postal.so/inboxes \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/inboxes', {
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/inboxes",
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('inboxes', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "list",
"has_more": false,
"data": [
{
"object": "inbox",
"id": "0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
"paused_by_plan": false,
"name": "Suporte",
"address": "[email protected]",
"domain_id": "0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f",
"receiving_address": "[email protected]",
"catch_all": false,
"forward_to": ["[email protected]"],
"can_forward": true,
"unread_count": 3,
"last_received_at": "2026-10-24T12:41:09.000000Z",
"created_at": "2026-10-02T18:20:00.000000Z",
"reply_name": "Ada do Suporte",
"view": "list",
"agent": {
"enabled": false,
"instructions": null,
"actions": [],
"tone": {
"formality": 0.3,
"length": 0.3
}
}
}
]
}
#Criar uma inbox
POST/inboxes
Com domain_id, a inbox recebe em local_part@seu-dominio (use * para receber qualquer endereço do domínio, um catch-all). Sem domain_id, a Postal gera um endereço em in.postal.so, como [email protected]. Inboxes no seu domínio estão disponíveis nos planos pagos.
Corpo da requisição
namestringobrigatórioNome da inbox, até 255 caracteres.domain_idstringDomínio seu com recebimento ativo (capabilities.receiving = "enabled").local_partstringParte antes do @, até 64 caracteres: letras, números,.,+,-e_, ou*. Obrigatório comdomain_id. Precisa começar com letra ou número (ou ser só*).forward_tostring[]Até 5 endereços para onde cada mensagem é encaminhada automaticamente (não vale para catch-all nem parain.postal.so).
Erros
- 422
validation_errorCampo inválido, domínio sem recebimento ativo ou endereço já em uso. - 403
plan_limit_reachedO plano não permite mais inboxes ou inboxes no seu domínio.
cURL
curl -X POST https://api.postal.so/inboxes \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Suporte",
"domain_id": "0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f",
"local_part": "suporte",
"forward_to": ["[email protected]"]
}'
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: 'Suporte',
domain_id: '0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f',
local_part: 'suporte',
forward_to: ['[email protected]'],
}),
});
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": "Suporte",
"domain_id": "0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f",
"local_part": "suporte",
"forward_to": ["[email protected]"],
},
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' => 'Suporte',
'domain_id' => '0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f',
'local_part' => 'suporte',
'forward_to' => ['[email protected]'],
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "inbox",
"id": "0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
"paused_by_plan": false,
"name": "Suporte",
"address": "[email protected]",
"domain_id": "0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f",
"receiving_address": "[email protected]",
"catch_all": false,
"forward_to": ["[email protected]"],
"can_forward": true,
"unread_count": 0,
"last_received_at": null,
"created_at": "2026-10-02T18:20:00.000000Z",
"reply_name": "Ada do Suporte",
"view": "list",
"agent": {
"enabled": false,
"instructions": null,
"actions": [],
"tone": {
"formality": 0.3,
"length": 0.3
}
}
}
#Obter uma inbox
GET/inboxes/{id}
receiving_address é um endereço em in.postal.so que também entrega na inbox: encaminhe e-mails de qualquer provedor para ele, sem apontar o MX do seu domínio (nas inboxes em in.postal.so é o próprio address). Além dos dados da inbox, traz folders com quantas conversas há em cada pasta (inbox, sent, drafts, archive, spam e trash) e quantas estão não lidas.
Parâmetros de caminho
idstringobrigatórioID da inbox.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
cURL
curl https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f', {
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/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
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('inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "inbox",
"id": "0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
"paused_by_plan": false,
"name": "Suporte",
"address": "[email protected]",
"domain_id": "0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f",
"receiving_address": "[email protected]",
"catch_all": false,
"forward_to": ["[email protected]"],
"can_forward": true,
"unread_count": 3,
"last_received_at": "2026-10-24T12:41:09.000000Z",
"created_at": "2026-10-02T18:20:00.000000Z",
"reply_name": "Ada do Suporte",
"view": "list",
"agent": {
"enabled": false,
"instructions": null,
"actions": [],
"tone": {
"formality": 0.3,
"length": 0.3
}
},
"folders": {
"inbox": {
"threads": 12,
"unread": 3
},
"sent": {
"threads": 5
},
"drafts": {
"threads": 1
},
"archive": {
"threads": 40,
"unread": 0
},
"spam": {
"threads": 2,
"unread": 2
},
"trash": {
"threads": 0,
"unread": 0
}
}
}
#Atualizar uma inbox
PATCH/inboxes/{id}
Muda o nome, os endereços de encaminhamento, o nome de resposta, o layout e o agente de IA. O endereço da inbox não muda.
Parâmetros de caminho
idstringobrigatórioID da inbox.
Corpo da requisição
namestringNovo nome.forward_tostring[] | nullAté 5 endereços.nullou[]desliga o encaminhamento.reply_namestring | nullNome de quem responde: as respostas saem como"Ada do Suporte" <suporte@…>. Até 100 caracteres.viewstringLayout da inbox no painel:list(padrão),compact,board(quadro por etiquetas),split(lista e leitura lado a lado) oucards(cartões).agentobjectO agente de IA da inbox (veja Agente de IA).enabledbooleanLiga ou desliga o agente.instructionsstring | nullO que o agente deve fazer, até 5.000 caracteres.actionsstring[]draft_reply,forward_thread,add_labels,assign_thread,mark_as_spam,archiveemove_to_trash.toneobjectformality(0 profissional, 1 descontraído) elength(0 conciso, 1 detalhado), de 0 a 1.
Erros
- 422
validation_errorAlgum campo é inválido. A mensagem diz qual. - 403
plan_limit_reachedO plano não inclui o agente de IA da inbox (ao ligaragent.enabled). - 403
paused_by_planA inbox está pausada porque passa do limite de inboxes do plano. - 404
not_foundO recurso não existe ou é de outro time.
cURL
curl -X PATCH https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"forward_to": ["[email protected]", "[email protected]"]
}'
Node.js
const response = await fetch('https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
forward_to: ['[email protected]', '[email protected]'],
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.patch(
"https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"forward_to": ["[email protected]", "[email protected]"],
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->patch('inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'forward_to' => ['[email protected]', '[email protected]'],
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "inbox",
"id": "0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
"paused_by_plan": false,
"name": "Suporte",
"address": "[email protected]",
"domain_id": "0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f",
"receiving_address": "[email protected]",
"catch_all": false,
"forward_to": ["[email protected]", "[email protected]"],
"can_forward": true,
"unread_count": 3,
"last_received_at": "2026-10-24T12:41:09.000000Z",
"created_at": "2026-10-02T18:20:00.000000Z",
"reply_name": "Ada do Suporte",
"view": "list",
"agent": {
"enabled": false,
"instructions": null,
"actions": [],
"tone": {
"formality": 0.3,
"length": 0.3
}
}
}
#Excluir uma inbox
DELETE/inboxes/{id}
Exclui a inbox e todas as mensagens recebidas nela.
Parâmetros de caminho
idstringobrigatórioID da inbox.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
cURL
curl -X DELETE https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
},
});
const data = await response.json();
Python
import os
import requests
response = requests.delete(
"https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
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->delete('inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "inbox",
"id": "0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
"deleted": true
}
#Listar e-mails recebidos
GET/emails/receiving
Lista as mensagens recebidas, da mais recente para a mais antiga. A lista traz o resumo; o conteúdo vem em GET /emails/receiving/{id}.
Cada mensagem traz otp_token: {"code", "origin"} quando o e-mail tem um cabeçalho OTP-Token válido, ou null. Veja Cabeçalho OTP-Token.
Parâmetros de consulta
limitintegerDe 1 a 100. Padrão: 20.afterstringCursor da próxima página.beforestringCursor da página anterior.inbox_idstringSó mensagens de uma inbox.unreadbooleanComtrue, só as não lidas.searchstringBusca em remetente, destinatários e assunto.
Erros
- 422
validation_errorAlgum campo é inválido. A mensagem diz qual.
cURL
curl 'https://api.postal.so/emails/receiving?inbox_id=0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f&unread=true' \
-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', {
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",
},
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',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "list",
"has_more": false,
"data": [
{
"object": "received_email",
"id": "019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d",
"inbox_id": "0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
"created_at": "2026-10-24T12:41:09.000000Z",
"from": "Carla Souza <[email protected]>",
"to": ["[email protected]"],
"cc": [],
"subject": "Troca de tamanho do pedido #10482",
"message_id": "<[email protected]>",
"read": false,
"size": 18342,
"has_attachments": true,
"otp_token": null
}
]
}
#Obter um e-mail recebido
GET/emails/receiving/{id}
Devolve a mensagem com HTML, texto, cabeçalhos e anexos. Os links de download dos anexos valem por 1 hora.
otp_token é {"code", "origin"} quando a mensagem tem exatamente um cabeçalho OTP-Token válido na seção de cabeçalhos do topo, e null em qualquer outro caso (também nos e-mails recebidos antes deste recurso). O código não vai no webhook email.received. Veja Cabeçalho OTP-Token.
Parâmetros de caminho
idstringobrigatórioID do e-mail recebido.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
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);
{
"object": "received_email",
"id": "019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d",
"inbox_id": "0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
"created_at": "2026-10-24T12:41:09.000000Z",
"from": "Carla Souza <[email protected]>",
"to": ["[email protected]"],
"cc": [],
"subject": "Troca de tamanho do pedido #10482",
"message_id": "<[email protected]>",
"read": false,
"size": 18342,
"has_attachments": true,
"otp_token": null,
"reply_to": [],
"html": "<p>Oi! Gostaria de trocar a camiseta do pedido #10482 pelo tamanho M.</p>",
"text": "Oi! Gostaria de trocar a camiseta do pedido #10482 pelo tamanho M.",
"headers": {
"date": "Fri, 24 Oct 2026 09:41:07 -0300",
"mime-version": "1.0"
},
"attachments": [
{
"id": "019a1c00-3e50-7b6c-8d7e-9f0a1b2c3d4e",
"filename": "label-photo.jpg",
"content_type": "image/jpeg",
"content_id": null,
"size": 15620,
"download_url": "https://api.postal.so/received-attachments/019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d/019a1c00-3e50-7b6c-8d7e-9f0a1b2c3d4e?expires=1761313269&signature=c0de…"
}
]
}
#Marcar como lido ou não lido
PATCH/emails/receiving/{id}
Parâmetros de caminho
idstringobrigatórioID do e-mail recebido.
Corpo da requisição
readbooleanobrigatóriotruemarca como lido;false, como não lido.
Erros
- 422
validation_errorAlgum campo é inválido. A mensagem diz qual. - 404
not_foundO recurso não existe ou é de outro time.
cURL
curl -X PATCH https://api.postal.so/emails/receiving/019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"read": true
}'
Node.js
const response = await fetch('https://api.postal.so/emails/receiving/019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
read: true,
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.patch(
"https://api.postal.so/emails/receiving/019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"read": True,
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->patch('emails/receiving/019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'read' => true,
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "received_email",
"id": "019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d",
"inbox_id": "0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
"created_at": "2026-10-24T12:41:09.000000Z",
"from": "Carla Souza <[email protected]>",
"to": ["[email protected]"],
"cc": [],
"subject": "Troca de tamanho do pedido #10482",
"message_id": "<[email protected]>",
"read": true,
"size": 18342,
"has_attachments": true,
"otp_token": null
}
#Excluir um e-mail recebido
DELETE/emails/receiving/{id}
Parâmetros de caminho
idstringobrigatórioID do e-mail recebido.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
cURL
curl -X DELETE 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', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
},
});
const data = await response.json();
Python
import os
import requests
response = requests.delete(
"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->delete('emails/receiving/019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "received_email",
"id": "019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d",
"deleted": true
}
#Mensagem recebida original
GET/emails/receiving/{id}/raw
A mensagem exatamente como chegou ao servidor (RFC 5322, text/plain).
Parâmetros de caminho
idstringobrigatórioID do e-mail recebido.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
cURL
curl https://api.postal.so/emails/receiving/019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d/raw \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/emails/receiving/019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d/raw', {
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
},
});
const message = await response.text();
Python
import os
import requests
response = requests.get(
"https://api.postal.so/emails/receiving/019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d/raw",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
timeout=10,
)
message = response.text
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->get('emails/receiving/019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d/raw', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$message = (string) $response->getBody();
Received: from mail.cliente.com.br (mail.cliente.com.br [203.0.113.24])
From: Carla Souza <[email protected]>
To: [email protected]
Subject: Troca de tamanho do pedido #10482
Message-ID: <[email protected]>
…
#Encaminhar um e-mail recebido
POST/emails/receiving/{id}/forward
Encaminha a mensagem para até 5 endereços, em segundo plano. Só funciona para inboxes num domínio seu verificado (não catch-all), porque o encaminhamento sai desse domínio.
Parâmetros de caminho
idstringobrigatórioID do e-mail recebido.
Corpo da requisição
tostring[]obrigatórioDe 1 a 5 endereços.
Erros
- 422
validation_errorEndereço inválido, ou a inbox não pode encaminhar. - 404
not_foundO recurso não existe ou é de outro time.
cURL
curl -X POST https://api.postal.so/emails/receiving/019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d/forward \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": ["[email protected]"]
}'
Node.js
const response = await fetch('https://api.postal.so/emails/receiving/019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d/forward', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: ['[email protected]'],
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/emails/receiving/019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d/forward",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"to": ["[email protected]"],
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('emails/receiving/019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d/forward', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'to' => ['[email protected]'],
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "received_email",
"id": "019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d",
"forwarded": true
}
#Conversas, pastas e etiquetas
Cada e-mail recebido entra numa conversa (thread): ele se junta à conversa que responde, pelos cabeçalhos In-Reply-To e References, casados com os e-mails recebidos e com as respostas enviadas pela inbox. Sem referência, abre uma conversa nova.
- Pastas:
inbox,archive,spametrash(campofolder) esent(conversas com resposta enviada pela inbox, fora da lixeira). Uma mensagem nova numa conversa arquivada a traz de volta para a Entrada; em Spam e na Lixeira ela continua onde está. - Etiquetas são por inbox, com cor, e agrupam conversas. Uma conversa pode ter várias.
- Atribuição: uma conversa pode ser atribuída a um membro do time (
assigned_to, oiddo usuário em sua conta). - Enviar: responder e abrir conversas novas passam pelo mesmo caminho do
POST /emails: o domínio precisa estar verificado e o envio conta na cota.
#Anexos ao escrever pela inbox
Responder, enviar uma mensagem nova, criar e atualizar um rascunho e enviar um rascunho aceitam attachments: [{"filename", "content", "content_type"}], com o conteúdo em Base64.
- Limites: até 10 arquivos por mensagem ou rascunho,
filenamede até 255 caracteres e 40 MB no total. A validação é a mesma doPOST /emails: Base64 inválido ou total acima do limite devolvem422 validation_error. - Rascunhos guardam os arquivos até serem enviados ou apagados (apagar o rascunho, ou a conversa de vez, apaga os arquivos). O conteúdo nunca volta na API: o rascunho lista
attachments: [{"id", "filename", "content_type", "size"}]. - Atualizar um rascunho: se o
PATCHtrouxerattachments, a lista é substituída. Para manter um anexo que o rascunho já tem, mande só oiddele ({"id": "…"}); itens comfilenameecontentsão adicionados e o que ficar de fora é removido. Sem o campo, os anexos não mudam;[]remove todos. - Mensagens enviadas listam os anexos em
attachments(id,filename,content_type,content_id,sizeedownload_url, um link assinado que vale por 1 hora), no mesmo formato dos anexos recebidos.
#Listar conversas
GET/inboxes/{id}/threads
Lista as conversas de uma pasta, da atividade mais recente para a mais antiga.
Parâmetros de caminho
idstringobrigatórioID da inbox.
Parâmetros de consulta
folderstringinbox(padrão),sent,archive,spamoutrash.labelstringSó conversas com esta etiqueta.unlabeledbooleanComtrue, só conversas sem etiqueta.assigned_tostringID de um membro,me(você) ounone(sem responsável).unreadbooleanComtrue, só as não lidas.searchstringBusca em assunto, trecho e remetentes.limitintegerDe 1 a 100. Padrão: 20.afterstringID da conversa para a próxima página.beforestringID da conversa para a página anterior.
Erros
- 422
validation_errorAlgum campo é inválido. A mensagem diz qual. - 404
not_foundO recurso não existe ou é de outro time.
cURL
curl 'https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/threads?folder=inbox&unread=true' \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/threads?folder=inbox&unread=true', {
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/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/threads",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
params={
"folder": "inbox",
"unread": "true",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->get('inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/threads', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'query' => [
'folder' => 'inbox',
'unread' => 'true',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "list",
"has_more": false,
"data": [
{
"object": "thread",
"id": "019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e",
"inbox_id": "0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
"subject": "Troca de tamanho do pedido #10482",
"folder": "inbox",
"read": false,
"unread_count": 1,
"message_count": 2,
"snippet": "Oi! Gostaria de trocar a camiseta do pedido #10482 pelo tamanho M.",
"participants": [
{
"name": "Carla Souza",
"email": "[email protected]",
"is_me": false
},
{
"name": "Ada do Suporte",
"email": "[email protected]",
"is_me": true
}
],
"has_attachments": true,
"has_draft": false,
"has_sent": true,
"last_message_at": "2026-10-24T13:02:11.000000Z",
"created_at": "2026-10-24T12:41:09.000000Z",
"assigned_to": {
"id": 12,
"name": "Carlos Lima",
"email": "[email protected]",
"avatar_url": null
},
"labels": [
{
"id": "019a1c01-0b21-7e32-9f43-1a2b3c4d5e6f",
"name": "Importante",
"color": "orange"
}
]
}
]
}
#Obter uma conversa
GET/inboxes/{id}/threads/{thread_id}
Devolve a conversa com todas as mensagens, da mais antiga para a mais nova (recebidas e enviadas, no mesmo formato) e os rascunhos. Os links de download dos anexos, recebidos e enviados, valem por 1 hora.
As mensagens recebidas (direction: "received") trazem otp_token, com o código do cabeçalho OTP-Token ou null, como em GET /emails/receiving/{id}. Veja Cabeçalho OTP-Token.
Parâmetros de caminho
idstringobrigatórioID da inbox.thread_idstringobrigatórioID da conversa.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
cURL
curl https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/threads/019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/threads/019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e', {
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/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/threads/019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e",
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('inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/threads/019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "thread",
"id": "019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e",
"inbox_id": "0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
"subject": "Troca de tamanho do pedido #10482",
"folder": "inbox",
"read": false,
"unread_count": 1,
"message_count": 2,
"snippet": "Oi! Gostaria de trocar a camiseta do pedido #10482 pelo tamanho M.",
"participants": [
{
"name": "Carla Souza",
"email": "[email protected]",
"is_me": false
},
{
"name": "Ada do Suporte",
"email": "[email protected]",
"is_me": true
}
],
"has_attachments": true,
"has_draft": false,
"has_sent": true,
"last_message_at": "2026-10-24T13:02:11.000000Z",
"created_at": "2026-10-24T12:41:09.000000Z",
"assigned_to": {
"id": 12,
"name": "Carlos Lima",
"email": "[email protected]",
"avatar_url": null
},
"labels": [
{
"id": "019a1c01-0b21-7e32-9f43-1a2b3c4d5e6f",
"name": "Importante",
"color": "orange"
}
],
"messages": [
{
"object": "thread_message",
"id": "019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d",
"thread_id": "019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e",
"direction": "received",
"from": "Carla Souza <[email protected]>",
"to": ["[email protected]"],
"cc": [],
"bcc": [],
"subject": "Troca de tamanho do pedido #10482",
"message_id": "<[email protected]>",
"created_at": "2026-10-24T12:41:09.000000Z",
"read": false,
"status": null,
"html": "<p>Oi! Gostaria de trocar a camiseta do pedido #10482 pelo tamanho M.</p>",
"text": "Oi! Gostaria de trocar a camiseta do pedido #10482 pelo tamanho M.",
"otp_token": null,
"attachments": []
},
{
"object": "thread_message",
"id": "019a1c03-2d43-7a54-9b65-3c4d5e6f7081",
"thread_id": "019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e",
"direction": "sent",
"from": "\"Ada do Suporte\" <[email protected]>",
"to": ["[email protected]"],
"cc": [],
"bcc": [],
"subject": "Re: Troca de tamanho do pedido #10482",
"message_id": "<[email protected]>",
"created_at": "2026-10-24T13:02:11.000000Z",
"read": true,
"status": "queued",
"html": null,
"text": "Oi, Carla! Já separamos o tamanho M para você.",
"attachments": [
{
"id": "019a1c05-4f65-7c76-9d87-5e6f70819203",
"filename": "tabela-de-medidas.pdf",
"content_type": "application/pdf",
"content_id": null,
"size": 48213,
"download_url": "https://api.postal.so/attachments/019a1c03-2d43-7a54-9b65-3c4d5e6f7081/019a1c05-4f65-7c76-9d87-5e6f70819203?expires=1761314531&signature=9a7e…"
}
]
}
],
"drafts": []
}
#Atualizar uma conversa
PATCH/inboxes/{id}/threads/{thread_id}
Move de pasta, marca como lida ou não lida, atribui a um membro e muda as etiquetas. Todos os campos são opcionais.
Parâmetros de caminho
idstringobrigatórioID da inbox.thread_idstringobrigatórioID da conversa.
Corpo da requisição
folderstringinbox,archive,spamoutrash.readbooleantruemarca todas as mensagens recebidas como lidas;falsemarca a última como não lida.assigned_tointeger | nullID de um membro do time, ounullpara tirar o responsável.labelsstring[]Substitui as etiquetas da conversa.add_labelsstring[]Acrescenta etiquetas.remove_labelsstring[]Tira etiquetas.
Erros
- 422
validation_errorAlgum campo é inválido. A mensagem diz qual. - 404
not_foundO recurso não existe ou é de outro time.
cURL
curl -X PATCH https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/threads/019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"folder": "archive",
"add_labels": ["019a1c01-0b21-7e32-9f43-1a2b3c4d5e6f"],
"read": true
}'
Node.js
const response = await fetch('https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/threads/019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
folder: 'archive',
add_labels: ['019a1c01-0b21-7e32-9f43-1a2b3c4d5e6f'],
read: true,
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.patch(
"https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/threads/019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"folder": "archive",
"add_labels": ["019a1c01-0b21-7e32-9f43-1a2b3c4d5e6f"],
"read": True,
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->patch('inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/threads/019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'folder' => 'archive',
'add_labels' => ['019a1c01-0b21-7e32-9f43-1a2b3c4d5e6f'],
'read' => true,
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "thread",
"id": "019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e",
"inbox_id": "0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
"subject": "Troca de tamanho do pedido #10482",
"folder": "archive",
"read": true,
"unread_count": 0,
"message_count": 2,
"snippet": "Oi! Gostaria de trocar a camiseta do pedido #10482 pelo tamanho M.",
"participants": [
{
"name": "Carla Souza",
"email": "[email protected]",
"is_me": false
},
{
"name": "Ada do Suporte",
"email": "[email protected]",
"is_me": true
}
],
"has_attachments": true,
"has_draft": false,
"has_sent": true,
"last_message_at": "2026-10-24T13:02:11.000000Z",
"created_at": "2026-10-24T12:41:09.000000Z",
"assigned_to": {
"id": 12,
"name": "Carlos Lima",
"email": "[email protected]",
"avatar_url": null
},
"labels": [
{
"id": "019a1c01-0b21-7e32-9f43-1a2b3c4d5e6f",
"name": "Importante",
"color": "orange"
}
]
}
#Excluir uma conversa
DELETE/inboxes/{id}/threads/{thread_id}
Move a conversa para a lixeira. Se ela já está na lixeira, apaga de vez, com as mensagens recebidas (as respostas enviadas continuam no registro de e-mails).
Parâmetros de caminho
idstringobrigatórioID da inbox.thread_idstringobrigatórioID da conversa.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
cURL
curl -X DELETE https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/threads/019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/threads/019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
},
});
const data = await response.json();
Python
import os
import requests
response = requests.delete(
"https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/threads/019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e",
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->delete('inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/threads/019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "thread",
"id": "019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e",
"deleted": true
}
#Agir em várias conversas
POST/inboxes/{id}/threads/batch
Parâmetros de caminho
idstringobrigatórioID da inbox.
Corpo da requisição
idsstring[]obrigatórioDe 1 a 100 IDs de conversas.actionstringobrigatórioread,unread,archive,spam,trash,inbox,delete,add_label,remove_labelouassign.label_idstringObrigatório emadd_labeleremove_label.assigned_tointeger | nullEmassign: o membro, ounull.
Erros
- 422
validation_errorAlgum campo é inválido. A mensagem diz qual. - 404
not_foundO recurso não existe ou é de outro time.
cURL
curl -X POST https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/threads/batch \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"ids": ["019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e"],
"action": "archive"
}'
Node.js
const response = await fetch('https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/threads/batch', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
ids: ['019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e'],
action: 'archive',
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/threads/batch",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"ids": ["019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e"],
"action": "archive",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/threads/batch', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'ids' => ['019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e'],
'action' => 'archive',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "batch",
"updated": 1
}
#Responder numa conversa
POST/inboxes/{id}/threads/{thread_id}/reply
Envia uma resposta a partir do endereço da inbox (com o reply_name), na mesma conversa, com In-Reply-To e References corretos. Sem to, responde ao Reply-To (ou ao remetente) da última mensagem recebida; sem subject, usa Re: …. Precisa de text ou html e de um domínio verificado. Aceita anexos.
Parâmetros de caminho
idstringobrigatórioID da inbox.thread_idstringobrigatórioID da conversa.
Corpo da requisição
textstringTexto da resposta.htmlstringHTML da resposta.tostring[]Destinatários.ccstring[]Com cópia.bccstring[]Com cópia oculta.subjectstringAssunto.draft_idstringRascunho que esta resposta envia (ele é apagado).attachmentsobject[]Anexos, até 10 arquivos e 40 MB no total, com as mesmas regras doPOST /emails. Aqui só há arquivos enviados no próprio corpo (sempathnemcontent_id).filenamestringobrigatórioNome do arquivo, até 255 caracteres.contentstringobrigatórioConteúdo do arquivo em Base64.content_typestringTipo MIME, comoapplication/pdf. Sem ele, é deduzido pela extensão.
Erros
- 422
validation_errorAlgum campo é inválido. A mensagem diz qual. - 404
not_foundO recurso não existe ou é de outro time. - 403
validation_errorO domínio da inbox não está verificado. - 403
sending_pausedOs envios do time estão pausados por taxa alta de bounces ou reclamações. - 403
paused_by_planO domínio da inbox está pausado pelo plano. - 403
plan_limit_reachedOs anexos passam do limite do plano. - 429
daily_quota_exceededA cota de envio do plano acabou (tambémmonthly_quota_exceeded).
cURL
curl -X POST https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/threads/019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e/reply \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "Oi, Carla! Já separamos o tamanho M para você.",
"attachments": [
{
"filename": "tabela-de-medidas.pdf",
"content": "JVBERi0xLjQK…",
"content_type": "application/pdf"
}
]
}'
Node.js
const response = await fetch('https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/threads/019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e/reply', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
text: 'Oi, Carla! Já separamos o tamanho M para você.',
attachments: [
{
filename: 'tabela-de-medidas.pdf',
content: 'JVBERi0xLjQK…',
content_type: 'application/pdf',
},
],
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/threads/019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e/reply",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"text": "Oi, Carla! Já separamos o tamanho M para você.",
"attachments": [
{
"filename": "tabela-de-medidas.pdf",
"content": "JVBERi0xLjQK…",
"content_type": "application/pdf",
},
],
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/threads/019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e/reply', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'text' => 'Oi, Carla! Já separamos o tamanho M para você.',
'attachments' => [
[
'filename' => 'tabela-de-medidas.pdf',
'content' => 'JVBERi0xLjQK…',
'content_type' => 'application/pdf',
],
],
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "thread_message",
"id": "019a1c03-2d43-7a54-9b65-3c4d5e6f7081",
"thread_id": "019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e",
"direction": "sent",
"from": "\"Ada do Suporte\" <[email protected]>",
"to": ["[email protected]"],
"cc": [],
"bcc": [],
"subject": "Re: Troca de tamanho do pedido #10482",
"message_id": "<[email protected]>",
"created_at": "2026-10-24T13:02:11.000000Z",
"read": true,
"status": "queued",
"html": null,
"text": "Oi, Carla! Já separamos o tamanho M para você.",
"attachments": [
{
"id": "019a1c05-4f65-7c76-9d87-5e6f70819203",
"filename": "tabela-de-medidas.pdf",
"content_type": "application/pdf",
"content_id": null,
"size": 48213,
"download_url": "https://api.postal.so/attachments/019a1c03-2d43-7a54-9b65-3c4d5e6f7081/019a1c05-4f65-7c76-9d87-5e6f70819203?expires=1761314531&signature=9a7e…"
}
]
}
#Enviar uma mensagem nova
POST/inboxes/{id}/messages
Envia uma mensagem nova a partir da inbox. Ela abre uma conversa (thread_id na resposta), que aparece na pasta sent. Aceita anexos.
Parâmetros de caminho
idstringobrigatórioID da inbox.
Corpo da requisição
tostring[]obrigatórioDestinatários.subjectstringobrigatórioAssunto.textstringTexto.htmlstringHTML.ccstring[]Com cópia.bccstring[]Com cópia oculta.attachmentsobject[]Anexos, até 10 arquivos e 40 MB no total, com as mesmas regras doPOST /emails. Aqui só há arquivos enviados no próprio corpo (sempathnemcontent_id).filenamestringobrigatórioNome do arquivo, até 255 caracteres.contentstringobrigatórioConteúdo do arquivo em Base64.content_typestringTipo MIME, comoapplication/pdf. Sem ele, é deduzido pela extensão.
Erros
- 422
validation_errorAlgum campo é inválido. A mensagem diz qual. - 404
not_foundO recurso não existe ou é de outro time. - 403
validation_errorO domínio da inbox não está verificado. - 403
sending_pausedOs envios do time estão pausados. - 403
paused_by_planO domínio da inbox está pausado pelo plano. - 429
daily_quota_exceededA cota de envio do plano acabou (tambémmonthly_quota_exceeded).
cURL
curl -X POST https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/messages \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": ["[email protected]"],
"subject": "Seu pedido foi enviado",
"text": "Oi, Carla! Seu pedido já saiu."
}'
Node.js
const response = await fetch('https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/messages', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: ['[email protected]'],
subject: 'Seu pedido foi enviado',
text: 'Oi, Carla! Seu pedido já saiu.',
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/messages",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"to": ["[email protected]"],
"subject": "Seu pedido foi enviado",
"text": "Oi, Carla! Seu pedido já saiu.",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/messages', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'to' => ['[email protected]'],
'subject' => 'Seu pedido foi enviado',
'text' => 'Oi, Carla! Seu pedido já saiu.',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "thread_message",
"id": "019a1c03-2d43-7a54-9b65-3c4d5e6f7081",
"thread_id": "019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e",
"direction": "sent",
"from": "\"Ada do Suporte\" <[email protected]>",
"to": ["[email protected]"],
"cc": [],
"bcc": [],
"subject": "Re: Troca de tamanho do pedido #10482",
"message_id": "<[email protected]>",
"created_at": "2026-10-24T13:02:11.000000Z",
"read": true,
"status": "queued",
"html": null,
"text": "Oi, Carla! Já separamos o tamanho M para você.",
"attachments": [
{
"id": "019a1c05-4f65-7c76-9d87-5e6f70819203",
"filename": "tabela-de-medidas.pdf",
"content_type": "application/pdf",
"content_id": null,
"size": 48213,
"download_url": "https://api.postal.so/attachments/019a1c03-2d43-7a54-9b65-3c4d5e6f7081/019a1c05-4f65-7c76-9d87-5e6f70819203?expires=1761314531&signature=9a7e…"
}
]
}
#Traduzir uma mensagem
POST/inboxes/{id}/threads/{thread_id}/messages/{message_id}/translate
Traduz uma mensagem da conversa (recebida ou enviada). É assíncrono, como os demais pedidos de IA: a resposta é uma tarefa; consulte GET /ai/tasks/{id} até ela ficar pronta. Usa 1 crédito de IA. O resultado é {"object": "ai_translation", "text", "subject", "from_language", "to_language", "credits"}.
Parâmetros de caminho
idstringobrigatórioID da inbox.thread_idstringobrigatórioID da conversa.message_idstringobrigatórioID da mensagem.
Corpo da requisição
tostringobrigatórioIdioma de destino:pt-BR,en,es,fr,de,it,ja,zh,ko,ru,nlouar.fromstringIdioma original. Sem ele, a IA detecta.
Erros
- 422
validation_errorAlgum campo é inválido. A mensagem diz qual. - 404
not_foundO recurso não existe ou é de outro time. - 429
ai_credits_exhaustedOs créditos de IA do mês acabaram.
cURL
curl -X POST https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/threads/019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e/messages/019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d/translate \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "en"
}'
Node.js
const response = await fetch('https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/threads/019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e/messages/019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d/translate', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: 'en',
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/threads/019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e/messages/019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d/translate",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"to": "en",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/threads/019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e/messages/019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d/translate', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'to' => 'en',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "ai_task",
"id": "019a1c04-3e54-7b65-8c76-4d5e6f708192",
"status": "pending"
}
#Listar etiquetas
GET/inboxes/{id}/labels
Parâmetros de caminho
idstringobrigatórioID da inbox.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
cURL
curl https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/labels \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/labels', {
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/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/labels",
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('inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/labels', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "list",
"has_more": false,
"data": [
{
"object": "inbox_label",
"id": "019a1c01-0b21-7e32-9f43-1a2b3c4d5e6f",
"inbox_id": "0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
"name": "Importante",
"color": "orange",
"thread_count": 2
}
]
}
#Criar uma etiqueta
POST/inboxes/{id}/labels
Parâmetros de caminho
idstringobrigatórioID da inbox.
Corpo da requisição
namestringobrigatórioAté 50 caracteres, único na inbox.colorstringcyan,teal,green,lime,yellow,orange,indigo,purple,pink,brownougray(padrão).
Erros
- 422
validation_errorNome repetido ou cor inválida. - 404
not_foundO recurso não existe ou é de outro time.
cURL
curl -X POST https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/labels \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Importante",
"color": "orange"
}'
Node.js
const response = await fetch('https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/labels', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Importante',
color: 'orange',
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/labels",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"name": "Importante",
"color": "orange",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/labels', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'name' => 'Importante',
'color' => 'orange',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "inbox_label",
"id": "019a1c01-0b21-7e32-9f43-1a2b3c4d5e6f",
"inbox_id": "0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
"name": "Importante",
"color": "orange",
"thread_count": 0
}
#Atualizar uma etiqueta
PATCH/inboxes/{id}/labels/{label_id}
Parâmetros de caminho
idstringobrigatórioID da inbox.label_idstringobrigatórioID da etiqueta.
Corpo da requisição
namestringNovo nome.colorstringNova cor.
Erros
- 422
validation_errorAlgum campo é inválido. A mensagem diz qual. - 404
not_foundO recurso não existe ou é de outro time.
cURL
curl -X PATCH https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/labels/019a1c01-0b21-7e32-9f43-1a2b3c4d5e6f \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"color": "pink"
}'
Node.js
const response = await fetch('https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/labels/019a1c01-0b21-7e32-9f43-1a2b3c4d5e6f', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
color: 'pink',
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.patch(
"https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/labels/019a1c01-0b21-7e32-9f43-1a2b3c4d5e6f",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"color": "pink",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->patch('inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/labels/019a1c01-0b21-7e32-9f43-1a2b3c4d5e6f', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'color' => 'pink',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "inbox_label",
"id": "019a1c01-0b21-7e32-9f43-1a2b3c4d5e6f",
"inbox_id": "0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
"name": "Importante",
"color": "pink",
"thread_count": 2
}
#Excluir uma etiqueta
DELETE/inboxes/{id}/labels/{label_id}
Tira a etiqueta de todas as conversas. As conversas continuam.
Parâmetros de caminho
idstringobrigatórioID da inbox.label_idstringobrigatórioID da etiqueta.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
cURL
curl -X DELETE https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/labels/019a1c01-0b21-7e32-9f43-1a2b3c4d5e6f \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/labels/019a1c01-0b21-7e32-9f43-1a2b3c4d5e6f', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
},
});
const data = await response.json();
Python
import os
import requests
response = requests.delete(
"https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/labels/019a1c01-0b21-7e32-9f43-1a2b3c4d5e6f",
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->delete('inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/labels/019a1c01-0b21-7e32-9f43-1a2b3c4d5e6f', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "inbox_label",
"id": "019a1c01-0b21-7e32-9f43-1a2b3c4d5e6f",
"deleted": true
}
#Listar rascunhos
GET/inboxes/{id}/drafts
Rascunhos da inbox, do mais recente para o mais antigo, com a lista de attachments de cada um (sem o conteúdo). Os que o agente de IA sugere têm source: "agent".
Parâmetros de caminho
idstringobrigatórioID da inbox.
Parâmetros de consulta
thread_idstringSó rascunhos de uma conversa.limitintegerDe 1 a 100. Padrão: 20.afterstringCursor da próxima página.beforestringCursor da página anterior.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
cURL
curl 'https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/drafts?thread_id=019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e' \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/drafts?thread_id=019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e', {
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/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/drafts",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
params={
"thread_id": "019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->get('inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/drafts', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'query' => [
'thread_id' => '019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "list",
"has_more": false,
"data": [
{
"object": "draft",
"id": "019a1c02-1c32-7f43-8a54-2b3c4d5e6f70",
"inbox_id": "0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
"thread_id": "019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e",
"source": "user",
"to": ["[email protected]"],
"cc": [],
"bcc": [],
"subject": "Re: Troca de tamanho do pedido #10482",
"text": "Oi, Carla! Já separamos o tamanho M para você.",
"html": null,
"attachments": [
{
"id": "019a1c05-4f65-7c76-9d87-5e6f70819203",
"filename": "tabela-de-medidas.pdf",
"content_type": "application/pdf",
"size": 48213
}
],
"created_by": {
"id": 12,
"name": "Carlos Lima"
},
"created_at": "2026-10-24T13:00:00.000000Z",
"updated_at": "2026-10-24T13:01:30.000000Z"
}
]
}
#Criar um rascunho
POST/inboxes/{id}/drafts
Parâmetros de caminho
idstringobrigatórioID da inbox.
Corpo da requisição
thread_idstringConversa a que o rascunho responde. Sem ela, é uma mensagem nova.tostring[]Destinatários.ccstring[]Com cópia.bccstring[]Com cópia oculta.subjectstringAssunto.textstringTexto.htmlstringHTML.attachmentsobject[]Anexos do rascunho, até 10 arquivos e 40 MB no total. Veja Anexos.filenamestringNome do arquivo, até 255 caracteres. Obrigatório semid.contentstringConteúdo do arquivo em Base64. Obrigatório semid.content_typestringTipo MIME. Sem ele, é deduzido pela extensão.idstringSó na atualização: ID de um anexo que o rascunho já tem, para mantê-lo (sem reenviar o conteúdo).
Erros
- 422
validation_errorAlgum campo é inválido. A mensagem diz qual. - 404
not_foundO recurso não existe ou é de outro time.
cURL
curl -X POST https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/drafts \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"thread_id": "019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e",
"to": ["[email protected]"],
"text": "Oi, Carla!"
}'
Node.js
const response = await fetch('https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/drafts', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
thread_id: '019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e',
to: ['[email protected]'],
text: 'Oi, Carla!',
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/drafts",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"thread_id": "019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e",
"to": ["[email protected]"],
"text": "Oi, Carla!",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/drafts', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'thread_id' => '019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e',
'to' => ['[email protected]'],
'text' => 'Oi, Carla!',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "draft",
"id": "019a1c02-1c32-7f43-8a54-2b3c4d5e6f70",
"inbox_id": "0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
"thread_id": "019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e",
"source": "user",
"to": ["[email protected]"],
"cc": [],
"bcc": [],
"subject": "Re: Troca de tamanho do pedido #10482",
"text": "Oi, Carla! Já separamos o tamanho M para você.",
"html": null,
"attachments": [
{
"id": "019a1c05-4f65-7c76-9d87-5e6f70819203",
"filename": "tabela-de-medidas.pdf",
"content_type": "application/pdf",
"size": 48213
}
],
"created_by": {
"id": 12,
"name": "Carlos Lima"
},
"created_at": "2026-10-24T13:00:00.000000Z",
"updated_at": "2026-10-24T13:01:30.000000Z"
}
#Obter um rascunho
GET/inboxes/{id}/drafts/{draft_id}
Devolve o rascunho com o texto, o HTML e a lista de attachments (sem o conteúdo dos arquivos). created_by é a pessoa que o escreveu, ou null quando foi o agente de IA (source: "agent").
Parâmetros de caminho
idstringobrigatórioID da inbox.draft_idstringobrigatórioID do rascunho.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
cURL
curl https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/drafts/019a1c02-1c32-7f43-8a54-2b3c4d5e6f70 \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/drafts/019a1c02-1c32-7f43-8a54-2b3c4d5e6f70', {
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/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/drafts/019a1c02-1c32-7f43-8a54-2b3c4d5e6f70",
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('inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/drafts/019a1c02-1c32-7f43-8a54-2b3c4d5e6f70', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "draft",
"id": "019a1c02-1c32-7f43-8a54-2b3c4d5e6f70",
"inbox_id": "0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
"thread_id": "019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e",
"source": "user",
"to": ["[email protected]"],
"cc": [],
"bcc": [],
"subject": "Re: Troca de tamanho do pedido #10482",
"text": "Oi, Carla! Já separamos o tamanho M para você.",
"html": null,
"attachments": [
{
"id": "019a1c05-4f65-7c76-9d87-5e6f70819203",
"filename": "tabela-de-medidas.pdf",
"content_type": "application/pdf",
"size": 48213
}
],
"created_by": {
"id": 12,
"name": "Carlos Lima"
},
"created_at": "2026-10-24T13:00:00.000000Z",
"updated_at": "2026-10-24T13:01:30.000000Z"
}
#Atualizar um rascunho
PATCH/inboxes/{id}/drafts/{draft_id}
Aceita os mesmos campos da criação, menos thread_id. Com attachments, a lista de anexos é substituída: mande {"id"} para manter um anexo que o rascunho já tem e filename com content para adicionar outro (veja Anexos).
Parâmetros de caminho
idstringobrigatórioID da inbox.draft_idstringobrigatórioID do rascunho.
Corpo da requisição
tostring[]Destinatários.ccstring[]Com cópia.bccstring[]Com cópia oculta.subjectstringAssunto.textstringTexto.htmlstringHTML.attachmentsobject[]Anexos do rascunho, até 10 arquivos e 40 MB no total. Veja Anexos.filenamestringNome do arquivo, até 255 caracteres. Obrigatório semid.contentstringConteúdo do arquivo em Base64. Obrigatório semid.content_typestringTipo MIME. Sem ele, é deduzido pela extensão.idstringSó na atualização: ID de um anexo que o rascunho já tem, para mantê-lo (sem reenviar o conteúdo).
Erros
- 422
validation_errorAlgum campo é inválido. A mensagem diz qual. - 404
not_foundO recurso não existe ou é de outro time.
cURL
curl -X PATCH https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/drafts/019a1c02-1c32-7f43-8a54-2b3c4d5e6f70 \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "Oi, Carla! Já separamos o tamanho M."
}'
Node.js
const response = await fetch('https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/drafts/019a1c02-1c32-7f43-8a54-2b3c4d5e6f70', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
text: 'Oi, Carla! Já separamos o tamanho M.',
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.patch(
"https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/drafts/019a1c02-1c32-7f43-8a54-2b3c4d5e6f70",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"text": "Oi, Carla! Já separamos o tamanho M.",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->patch('inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/drafts/019a1c02-1c32-7f43-8a54-2b3c4d5e6f70', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'text' => 'Oi, Carla! Já separamos o tamanho M.',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "draft",
"id": "019a1c02-1c32-7f43-8a54-2b3c4d5e6f70",
"inbox_id": "0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
"thread_id": "019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e",
"source": "user",
"to": ["[email protected]"],
"cc": [],
"bcc": [],
"subject": "Re: Troca de tamanho do pedido #10482",
"text": "Oi, Carla! Já separamos o tamanho M.",
"html": null,
"attachments": [
{
"id": "019a1c05-4f65-7c76-9d87-5e6f70819203",
"filename": "tabela-de-medidas.pdf",
"content_type": "application/pdf",
"size": 48213
}
],
"created_by": {
"id": 12,
"name": "Carlos Lima"
},
"created_at": "2026-10-24T13:00:00.000000Z",
"updated_at": "2026-10-24T13:01:30.000000Z"
}
#Enviar um rascunho
POST/inboxes/{id}/drafts/{draft_id}/send
Envia o rascunho (como resposta, se ele pertence a uma conversa; como mensagem nova, se não) com os anexos do rascunho e o apaga, junto com os arquivos dele.
Parâmetros de caminho
idstringobrigatórioID da inbox.draft_idstringobrigatórioID do rascunho.
Erros
- 422
validation_errorAlgum campo é inválido. A mensagem diz qual. - 404
not_foundO recurso não existe ou é de outro time. - 403
validation_errorO domínio da inbox não está verificado. - 403
sending_pausedOs envios do time estão pausados. - 429
daily_quota_exceededA cota de envio do plano acabou (tambémmonthly_quota_exceeded).
cURL
curl -X POST https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/drafts/019a1c02-1c32-7f43-8a54-2b3c4d5e6f70/send \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/drafts/019a1c02-1c32-7f43-8a54-2b3c4d5e6f70/send', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
},
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/drafts/019a1c02-1c32-7f43-8a54-2b3c4d5e6f70/send",
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->post('inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/drafts/019a1c02-1c32-7f43-8a54-2b3c4d5e6f70/send', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "thread_message",
"id": "019a1c03-2d43-7a54-9b65-3c4d5e6f7081",
"thread_id": "019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e",
"direction": "sent",
"from": "\"Ada do Suporte\" <[email protected]>",
"to": ["[email protected]"],
"cc": [],
"bcc": [],
"subject": "Re: Troca de tamanho do pedido #10482",
"message_id": "<[email protected]>",
"created_at": "2026-10-24T13:02:11.000000Z",
"read": true,
"status": "queued",
"html": null,
"text": "Oi, Carla! Já separamos o tamanho M para você.",
"attachments": [
{
"id": "019a1c05-4f65-7c76-9d87-5e6f70819203",
"filename": "tabela-de-medidas.pdf",
"content_type": "application/pdf",
"content_id": null,
"size": 48213,
"download_url": "https://api.postal.so/attachments/019a1c03-2d43-7a54-9b65-3c4d5e6f7081/019a1c05-4f65-7c76-9d87-5e6f70819203?expires=1761314531&signature=9a7e…"
}
]
}
#Excluir um rascunho
DELETE/inboxes/{id}/drafts/{draft_id}
Parâmetros de caminho
idstringobrigatórioID da inbox.draft_idstringobrigatórioID do rascunho.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
cURL
curl -X DELETE https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/drafts/019a1c02-1c32-7f43-8a54-2b3c4d5e6f70 \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/drafts/019a1c02-1c32-7f43-8a54-2b3c4d5e6f70', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
},
});
const data = await response.json();
Python
import os
import requests
response = requests.delete(
"https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/drafts/019a1c02-1c32-7f43-8a54-2b3c4d5e6f70",
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->delete('inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/drafts/019a1c02-1c32-7f43-8a54-2b3c4d5e6f70', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "draft",
"id": "019a1c02-1c32-7f43-8a54-2b3c4d5e6f70",
"deleted": true
}
#Agente de IA
Com o agente ligado (agent.enabled em PATCH /inboxes/{id}), cada e-mail novo é lido por uma IA que segue as suas instructions e o tone, e só pode fazer o que está em actions: sugerir uma resposta (draft_reply), aplicar etiquetas e criar as que faltam (add_labels), atribuir (assign_thread), arquivar (archive), marcar como spam (mark_as_spam), mover para a lixeira (move_to_trash) e encaminhar (forward_thread).
- Nunca envia nada sozinho: as respostas viram rascunhos (
source: "agent") para uma pessoa revisar e enviar. - Só encaminha para endereços escritos nas instruções.
- Usa 1 crédito de IA por e-mail. Não faz nada sem créditos, com a inbox ou o agente desligados, em e-mails automáticos (
Auto-Submitted) ou enviados pela própria inbox. - Para escrever boas
instructions, usePOST /inboxes/{id}/agent/improve-instructions, que reescreve o seu rascunho com mais clareza.
#Melhorar as instruções do agente
POST/inboxes/{id}/agent/improve-instructions
Reescreve o rascunho das instruções do agente para ficar mais claro e específico, no mesmo idioma e com a mesma intenção, sem inventar fatos: endereços de e-mail, preços, horários e regras ficam como você escreveu. Não salva nada: guarde o resultado com PATCH /inboxes/{id} (agent.instructions) depois de revisar.
É assíncrono, como os demais pedidos de IA: a resposta é uma tarefa (ai_task) e o resultado chega por GET /ai/tasks/{id} como {"object": "agent_instructions", "instructions", "task_id", "status": "completed", "credits"}. Usa 1 crédito de IA.
Parâmetros de caminho
idstringobrigatórioID da inbox.
Corpo da requisição
instructionsstringobrigatórioO rascunho das instruções, de 1 a 4.000 caracteres.
Erros
- 404
not_foundO recurso não existe ou é de outro time. - 422
validation_errorCampo ausente ou fora dos limites. - 429
ai_credits_exhaustedOs créditos de IA do mês acabaram. Eles renovam no dia 1º (UTC). - 503
ai_not_configuredO assistente de IA não está disponível.
cURL
curl -X POST https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/agent/improve-instructions \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"instructions": "responde as duvidas dos clientes e manda cobranca pro [email protected]"
}'
Node.js
const response = await fetch('https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/agent/improve-instructions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
instructions: 'responde as duvidas dos clientes e manda cobranca pro [email protected]',
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/agent/improve-instructions",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"instructions": "responde as duvidas dos clientes e manda cobranca pro [email protected]",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('inboxes/0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f/agent/improve-instructions', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'instructions' => 'responde as duvidas dos clientes e manda cobranca pro [email protected]',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "ai_task",
"id": "019a1d40-2c3d-7e4f-8a5b-6c7d8e9f0a1b",
"status": "pending"
}
{
"object": "agent_instructions",
"instructions": "Responda com educação às dúvidas dos clientes sobre pedidos, trocas e entregas.\nEncaminhe os assuntos de cobrança para [email protected].",
"task_id": "019a1d40-2c3d-7e4f-8a5b-6c7d8e9f0a1b",
"status": "completed",
"credits": {
"used": 13,
"limit": 100,
"remaining": 87,
"resets_at": "2026-11-01T00:00:00Z"
}
}
#Webhooks da inbox
Além de email.received, quatro eventos acompanham as conversas. Assine-os em POST /webhooks; os formatos estão em Webhooks.
| Evento | Quando |
|---|---|
inbox.thread.created | Uma conversa nova foi aberta, por um e-mail recebido ou por uma mensagem enviada pela inbox. |
inbox.thread.updated | A pasta, o estado de lida, o responsável ou as etiquetas mudaram (por PATCH, ação em lote, exclusão para a lixeira ou pelo agente), ou um e-mail novo tirou a conversa do arquivo. Um evento por mudança, e uma por conversa alterada nas ações em lote. |
inbox.thread.deleted | A conversa foi apagada de vez. |
inbox.draft.created | O agente de IA criou um rascunho (source: "agent"). Rascunhos criados por pessoas não geram evento. |
#Limites do plano
O plano define quantas inboxes o time tem e quais recursos delas pode usar. Passar de um limite responde 403 plan_limit_reached, com uma mensagem que diz o que falta. Os valores de cada plano estão em Limites e em GET /plans.
| Recurso do plano | Chave | O que muda na API |
|---|---|---|
| Inboxes | inboxes | Quantas inboxes o time pode ter. Acima disso, POST /inboxes responde 403. |
| Inboxes no seu domínio | custom_inbound_domains | Sem ele, só há inboxes em in.postal.so: criar uma com domain_id responde 403, e o domínio não pode ligar o recebimento. |
| Agente de IA da inbox | inbox_agent | Ligar agent.enabled em PATCH /inboxes/{id} responde 403 sem ele. |
| Créditos de IA | ai_credits | O agente e a tradução usam créditos por mês; sem créditos, a tradução responde 429 ai_credits_exhausted e o agente não age. |
Se o plano diminui e o time fica com mais inboxes do que o novo limite, a Postal não apaga nenhuma: as mais antigas, até o limite, continuam ativas e as mais novas ficam pausadas (paused_by_plan: true na inbox). Uma inbox pausada continua na lista e pode ser lida, mas PATCH /inboxes/{id} responde 403 paused_by_plan e o agente de IA dela não age. Ela volta ao normal quando o plano permite de novo ou quando você apaga outras inboxes. Veja também Limites do plano em E-mails.