Recibidos y buzones de entrada
Buzones de entrada para recibir correos electrónicos y los endpoints para leer, reenviar y organizar los mensajes recibidos.
Recibe correos electrónicos en Postal: crea buzones de entrada en un dominio tuyo (con recepción activa) o en una dirección generada en in.postal.so, lee los mensajes por la API y recibe un aviso al instante mediante el webhook email.received. Consulta cómo apuntar el MX en Dominios y DNS. Postal acepta mensajes de hasta 25 MB. Los endpoints de esta página exigen una clave de acceso total, y el plan define cuántos buzones de entrada, y qué funciones, tiene el equipo (consulta Límites del plan).
#Listar buzones de entrada
GET/inboxes
Lista los buzones de entrada del equipo con el conteo de mensajes no leídos. Acepta limit, after y before.
Parámetros de consulta
limitintegerDe 1 a 100. Predeterminado: 20.afterstringCursor de la página siguiente.beforestringCursor de la 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
}
}
}
]
}
#Crear un buzón de entrada
POST/inboxes
Con domain_id, el buzón recibe en local_part@seu-dominio (usa * para recibir cualquier dirección del dominio, un catch-all). Sin domain_id, Postal genera una dirección en in.postal.so, como [email protected]. Los buzones en tu dominio están disponibles en los planes de pago.
Cuerpo de la solicitud
namestringobligatorioNombre del buzón, hasta 255 caracteres.domain_idstringDominio tuyo con recepción activa (capabilities.receiving = "enabled").local_partstringParte anterior a la @, hasta 64 caracteres: letras, números,.,+,-y_, o*. Obligatorio condomain_id. Debe empezar por letra o número (o ser solo*).forward_tostring[]Hasta 5 direcciones a las que se reenvía automáticamente cada mensaje (no vale para catch-all ni parain.postal.so).
Errores
- 422
validation_errorCampo no válido, dominio sin recepción activa o dirección ya en uso. - 403
plan_limit_reachedEl plan no permite más buzones de entrada ni buzones en tu dominio.
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
}
}
}
#Obtener un buzón de entrada
GET/inboxes/{id}
receiving_address es una dirección en in.postal.so que también entrega en el buzón: reenvía correos electrónicos de cualquier proveedor a ella, sin apuntar el MX de tu dominio (en los buzones en in.postal.so es la propia address). Además de los datos del buzón, trae folders con cuántas conversaciones hay en cada carpeta (inbox, sent, drafts, archive, spam y trash) y cuántas están sin leer.
Parámetros de ruta
idstringobligatorioID del buzón de entrada.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo.
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
}
}
}
#Actualizar un buzón de entrada
PATCH/inboxes/{id}
Cambia el nombre, las direcciones de reenvío, el nombre de respuesta, el diseño y el agente de IA. La dirección del buzón no cambia.
Parámetros de ruta
idstringobligatorioID del buzón de entrada.
Cuerpo de la solicitud
namestringNombre nuevo.forward_tostring[] | nullHasta 5 direcciones.nullo[]desactiva el reenvío.reply_namestring | nullNombre de quien responde: las respuestas salen como"Ada do Suporte" <suporte@…>. Hasta 100 caracteres.viewstringDiseño del buzón en el panel:list(predeterminado),compact,board(tablero por etiquetas),split(lista y lectura lado a lado) ocards(tarjetas).agentobjectEl agente de IA del buzón (consulta Agente de IA).enabledbooleanActiva o desactiva el agente.instructionsstring | nullLo que debe hacer el agente, hasta 5.000 caracteres.actionsstring[]draft_reply,forward_thread,add_labels,assign_thread,mark_as_spam,archiveymove_to_trash.toneobjectformality(0 profesional, 1 desenfadado) ylength(0 conciso, 1 detallado), de 0 a 1.
Errores
- 422
validation_errorAlgún campo no es válido. El mensaje indica cuál. - 403
plan_limit_reachedEl plan no incluye el agente de IA del buzón de entrada (al activaragent.enabled). - 403
paused_by_planEl buzón está en pausa porque supera el límite de buzones de entrada del plan. - 404
not_foundEl recurso no existe o es de otro equipo.
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
}
}
}
#Eliminar un buzón de entrada
DELETE/inboxes/{id}
Elimina el buzón y todos los mensajes recibidos en él.
Parámetros de ruta
idstringobligatorioID del buzón de entrada.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo.
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 correos electrónicos recibidos
GET/emails/receiving
Lista los mensajes recibidos, del más reciente al más antiguo. La lista trae el resumen; el contenido viene en GET /emails/receiving/{id}.
Cada mensaje trae otp_token: {"code", "origin"} cuando el correo electrónico tiene un encabezado OTP-Token válido, o null. Consulta Encabezado OTP-Token.
Parámetros de consulta
limitintegerDe 1 a 100. Predeterminado: 20.afterstringCursor de la página siguiente.beforestringCursor de la página anterior.inbox_idstringSolo mensajes de un buzón de entrada.unreadbooleanContrue, solo los no leídos.searchstringBúsqueda en remitente, destinatarios y asunto.
Errores
- 422
validation_errorAlgún campo no es válido. El mensaje indica cuál.
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
}
]
}
#Obtener un correo electrónico recibido
GET/emails/receiving/{id}
Devuelve el mensaje con HTML, texto, encabezados y adjuntos. Los enlaces de descarga de los adjuntos valen durante 1 hora.
otp_token es {"code", "origin"} cuando el mensaje tiene exactamente un encabezado OTP-Token válido en la sección de encabezados superior, y null en cualquier otro caso (también en los correos electrónicos recibidos antes de esta función). El código no va en el webhook email.received. Consulta Encabezado OTP-Token.
Parámetros de ruta
idstringobligatorioID del correo electrónico recibido.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo.
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 leído o no leído
PATCH/emails/receiving/{id}
Parámetros de ruta
idstringobligatorioID del correo electrónico recibido.
Cuerpo de la solicitud
readbooleanobligatoriotruemarca como leído;false, como no leído.
Errores
- 422
validation_errorAlgún campo no es válido. El mensaje indica cuál. - 404
not_foundEl recurso no existe o es de otro equipo.
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
}
#Eliminar un correo electrónico recibido
DELETE/emails/receiving/{id}
Parámetros de ruta
idstringobligatorioID del correo electrónico recibido.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo.
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
}
#Mensaje recibido original
GET/emails/receiving/{id}/raw
El mensaje exactamente como llegó al servidor (RFC 5322, text/plain).
Parámetros de ruta
idstringobligatorioID del correo electrónico recibido.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo.
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]>
…
#Reenviar un correo electrónico recibido
POST/emails/receiving/{id}/forward
Reenvía el mensaje a un máximo de 5 direcciones, en segundo plano. Solo funciona para buzones de entrada de un dominio tuyo verificado (no catch-all), porque el reenvío sale de ese dominio.
Parámetros de ruta
idstringobligatorioID del correo electrónico recibido.
Cuerpo de la solicitud
tostring[]obligatorioDe 1 a 5 direcciones.
Errores
- 422
validation_errorDirección no válida, o el buzón de entrada no puede reenviar. - 404
not_foundEl recurso no existe o es de otro equipo.
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
}
#Conversaciones, carpetas y etiquetas
Cada correo recibido entra en una conversación (thread): se une a la conversación a la que responde, mediante los encabezados In-Reply-To y References, que se cotejan con los correos recibidos y con las respuestas enviadas desde el buzón de entrada. Sin referencia, abre una conversación nueva.
- Carpetas:
inbox,archive,spamytrash(campofolder) ysent(conversaciones con una respuesta enviada desde el buzón de entrada, fuera de la papelera). Un mensaje nuevo en una conversación archivada la devuelve a la Bandeja de entrada; en Spam y en la Papelera se queda donde está. - Las etiquetas son por buzón de entrada, tienen color y agrupan conversaciones. Una conversación puede tener varias.
- Asignación: una conversación puede asignarse a un miembro del equipo (
assigned_to, eliddel usuario en tu cuenta). - Enviar: responder y abrir conversaciones nuevas siguen el mismo camino que
POST /emails: el dominio debe estar verificado y el envío cuenta en la cuota.
#Adjuntos al escribir desde el buzón de entrada
Responder, enviar un mensaje nuevo, crear y actualizar un borrador y enviar un borrador aceptan attachments: [{"filename", "content", "content_type"}], con el contenido en Base64.
- Límites: hasta 10 archivos por mensaje o borrador,
filenamede hasta 255 caracteres y 40 MB en total. La validación es la misma que la dePOST /emails: un Base64 no válido o un total por encima del límite devuelven422 validation_error. - Los borradores conservan los archivos hasta que se envían o se eliminan (eliminar el borrador, o la conversación definitivamente, elimina los archivos). El contenido nunca vuelve por la API: el borrador lista
attachments: [{"id", "filename", "content_type", "size"}]. - Actualizar un borrador: si el
PATCHincluyeattachments, la lista se sustituye. Para conservar un adjunto que el borrador ya tiene, envía solo suid({"id": "…"}); los elementos confilenameycontentse añaden y lo que quede fuera se elimina. Sin el campo, los adjuntos no cambian;[]elimina todos. - Los mensajes enviados listan los adjuntos en
attachments(id,filename,content_type,content_id,sizeydownload_url, un enlace firmado que vale por 1 hora), con el mismo formato que los adjuntos recibidos.
#Listar conversaciones
GET/inboxes/{id}/threads
Lista las conversaciones de una carpeta, de la actividad más reciente a la más antigua.
Parámetros de ruta
idstringobligatorioID del buzón de entrada.
Parámetros de consulta
folderstringinbox(predeterminado),sent,archive,spamotrash.labelstringSolo conversaciones con esta etiqueta.unlabeledbooleanContrue, solo conversaciones sin etiqueta.assigned_tostringID de un miembro,me(tú) onone(sin responsable).unreadbooleanContrue, solo los no leídos.searchstringBusca en el asunto, el fragmento y los remitentes.limitintegerDe 1 a 100. Predeterminado: 20.afterstringID de la conversación para la página siguiente.beforestringID de la conversación para la página anterior.
Errores
- 422
validation_errorAlgún campo no es válido. El mensaje indica cuál. - 404
not_foundEl recurso no existe o es de otro equipo.
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"
}
]
}
]
}
#Obtener una conversación
GET/inboxes/{id}/threads/{thread_id}
Devuelve la conversación con todos los mensajes, del más antiguo al más nuevo (recibidos y enviados, con el mismo formato) y los borradores. Los enlaces de descarga de los adjuntos, recibidos y enviados, valen por 1 hora.
Los mensajes recibidos (direction: "received") incluyen otp_token, con el código del encabezado OTP-Token o null, como en GET /emails/receiving/{id}. Consulta Encabezado OTP-Token.
Parámetros de ruta
idstringobligatorioID del buzón de entrada.thread_idstringobligatorioID de la conversación.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo.
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": []
}
#Actualizar una conversación
PATCH/inboxes/{id}/threads/{thread_id}
Mueve de carpeta, marca como leída o no leída, asigna a un miembro y cambia las etiquetas. Todos los campos son opcionales.
Parámetros de ruta
idstringobligatorioID del buzón de entrada.thread_idstringobligatorioID de la conversación.
Cuerpo de la solicitud
folderstringinbox,archive,spamotrash.readbooleantruemarca todos los mensajes recibidos como leídos;falsemarca el último como no leído.assigned_tointeger | nullID de un miembro del equipo, onullpara quitar al responsable.labelsstring[]Sustituye las etiquetas de la conversación.add_labelsstring[]Añade etiquetas.remove_labelsstring[]Quita etiquetas.
Errores
- 422
validation_errorAlgún campo no es válido. El mensaje indica cuál. - 404
not_foundEl recurso no existe o es de otro equipo.
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"
}
]
}
#Eliminar una conversación
DELETE/inboxes/{id}/threads/{thread_id}
Mueve la conversación a la papelera. Si ya está en la papelera, la elimina definitivamente, junto con los mensajes recibidos (las respuestas enviadas permanecen en el registro de correos).
Parámetros de ruta
idstringobligatorioID del buzón de entrada.thread_idstringobligatorioID de la conversación.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo.
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
}
#Actuar sobre varias conversaciones
POST/inboxes/{id}/threads/batch
Parámetros de ruta
idstringobligatorioID del buzón de entrada.
Cuerpo de la solicitud
idsstring[]obligatorioDe 1 a 100 IDs de conversaciones.actionstringobligatorioread,unread,archive,spam,trash,inbox,delete,add_label,remove_labeloassign.label_idstringObligatorio enadd_labelyremove_label.assigned_tointeger | nullEnassign: el miembro, onull.
Errores
- 422
validation_errorAlgún campo no es válido. El mensaje indica cuál. - 404
not_foundEl recurso no existe o es de otro equipo.
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 en una conversación
POST/inboxes/{id}/threads/{thread_id}/reply
Envía una respuesta desde la dirección del buzón de entrada (con el reply_name), en la misma conversación, con In-Reply-To y References correctos. Sin to, responde al Reply-To (o al remitente) del último mensaje recibido; sin subject, usa Re: …. Requiere text o html y un dominio verificado. Acepta adjuntos.
Parámetros de ruta
idstringobligatorioID del buzón de entrada.thread_idstringobligatorioID de la conversación.
Cuerpo de la solicitud
textstringTexto de la respuesta.htmlstringHTML de la respuesta.tostring[]Destinatários.ccstring[]Con copia.bccstring[]Con copia oculta.subjectstringAssunto.draft_idstringBorrador que envía esta respuesta (se elimina).attachmentsobject[]Adjuntos, hasta 10 archivos y 40 MB en total, con las mismas reglas quePOST /emails. Aquí solo se admiten archivos enviados en el propio cuerpo (sinpathnicontent_id).filenamestringobligatorioNombre del archivo, hasta 255 caracteres.contentstringobligatorioContenido del archivo en Base64.content_typestringTipo MIME, comoapplication/pdf. Si se omite, se deduce de la extensión.
Errores
- 422
validation_errorAlgún campo no es válido. El mensaje indica cuál. - 404
not_foundEl recurso no existe o es de otro equipo. - 403
validation_errorEl dominio del buzón de entrada no está verificado. - 403
sending_pausedLos envíos del equipo están pausados por una tasa alta de rebotes o quejas. - 403
paused_by_planEl dominio del buzón de entrada está pausado por el plan. - 403
plan_limit_reachedLos adjuntos superan el límite del plan. - 429
daily_quota_exceededLa cuota de envío del plan se agotó (tambiénmonthly_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 un mensaje nuevo
POST/inboxes/{id}/messages
Envía un mensaje nuevo desde el buzón de entrada. Abre una conversación (thread_id en la respuesta), que aparece en la carpeta sent. Acepta adjuntos.
Parámetros de ruta
idstringobligatorioID del buzón de entrada.
Cuerpo de la solicitud
tostring[]obligatorioDestinatários.subjectstringobligatorioAssunto.textstringTexto.htmlstringHTML.ccstring[]Con copia.bccstring[]Con copia oculta.attachmentsobject[]Adjuntos, hasta 10 archivos y 40 MB en total, con las mismas reglas quePOST /emails. Aquí solo se admiten archivos enviados en el propio cuerpo (sinpathnicontent_id).filenamestringobligatorioNombre del archivo, hasta 255 caracteres.contentstringobligatorioContenido del archivo en Base64.content_typestringTipo MIME, comoapplication/pdf. Si se omite, se deduce de la extensión.
Errores
- 422
validation_errorAlgún campo no es válido. El mensaje indica cuál. - 404
not_foundEl recurso no existe o es de otro equipo. - 403
validation_errorEl dominio del buzón de entrada no está verificado. - 403
sending_pausedLos envíos del equipo están en pausa. - 403
paused_by_planEl dominio del buzón de entrada está pausado por el plan. - 429
daily_quota_exceededLa cuota de envío del plan se agotó (tambiénmonthly_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…"
}
]
}
#Traducir un mensaje
POST/inboxes/{id}/threads/{thread_id}/messages/{message_id}/translate
Traduce un mensaje de la conversación (recibido o enviado). Es asíncrono, como las demás solicitudes de IA: la respuesta es una tarea; consulta GET /ai/tasks/{id} hasta que esté lista. Usa 1 crédito de IA. El resultado es {"object": "ai_translation", "text", "subject", "from_language", "to_language", "credits"}.
Parámetros de ruta
idstringobligatorioID del buzón de entrada.thread_idstringobligatorioID de la conversación.message_idstringobligatorioID del mensaje.
Cuerpo de la solicitud
tostringobligatorioIdioma de destino:pt-BR,en,es,fr,de,it,ja,zh,ko,ru,nloar.fromstringIdioma original. Si se omite, la IA lo detecta.
Errores
- 422
validation_errorAlgún campo no es válido. El mensaje indica cuál. - 404
not_foundEl recurso no existe o es de otro equipo. - 429
ai_credits_exhaustedLos créditos de IA del mes se agotaron.
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 ruta
idstringobligatorioID del buzón de entrada.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo.
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
}
]
}
#Crear una etiqueta
POST/inboxes/{id}/labels
Parámetros de ruta
idstringobligatorioID del buzón de entrada.
Cuerpo de la solicitud
namestringobligatorioHasta 50 caracteres, único en el buzón de entrada.colorstringcyan,teal,green,lime,yellow,orange,indigo,purple,pink,brownogray(predeterminado).
Errores
- 422
validation_errorNombre repetido o color no válido. - 404
not_foundEl recurso no existe o es de otro equipo.
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
}
#Actualizar una etiqueta
PATCH/inboxes/{id}/labels/{label_id}
Parámetros de ruta
idstringobligatorioID del buzón de entrada.label_idstringobligatorioID de la etiqueta.
Cuerpo de la solicitud
namestringNombre nuevo.colorstringNuevo color.
Errores
- 422
validation_errorAlgún campo no es válido. El mensaje indica cuál. - 404
not_foundEl recurso no existe o es de otro equipo.
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
}
#Eliminar una etiqueta
DELETE/inboxes/{id}/labels/{label_id}
Quita la etiqueta de todas las conversaciones. Las conversaciones permanecen.
Parámetros de ruta
idstringobligatorioID del buzón de entrada.label_idstringobligatorioID de la etiqueta.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo.
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 borradores
GET/inboxes/{id}/drafts
Borradores del buzón de entrada, del más reciente al más antiguo, con la lista de attachments de cada uno (sin el contenido). Los que sugiere el agente de IA tienen source: "agent".
Parámetros de ruta
idstringobligatorioID del buzón de entrada.
Parámetros de consulta
thread_idstringSolo borradores de una conversación.limitintegerDe 1 a 100. Predeterminado: 20.afterstringCursor de la página siguiente.beforestringCursor de la página anterior.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo.
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"
}
]
}
#Crear un borrador
POST/inboxes/{id}/drafts
Parámetros de ruta
idstringobligatorioID del buzón de entrada.
Cuerpo de la solicitud
thread_idstringConversación a la que responde el borrador. Sin ella, es un mensaje nuevo.tostring[]Destinatários.ccstring[]Con copia.bccstring[]Con copia oculta.subjectstringAssunto.textstringTexto.htmlstringHTML.attachmentsobject[]Adjuntos del borrador, hasta 10 archivos y 40 MB en total. Consulta Adjuntos.filenamestringNombre del archivo, hasta 255 caracteres. Obligatorio sinid.contentstringContenido del archivo en Base64. Obligatorio sinid.content_typestringTipo MIME. Si se omite, se deduce de la extensión.idstringSolo al actualizar: ID de un adjunto que el borrador ya tiene, para conservarlo (sin reenviar el contenido).
Errores
- 422
validation_errorAlgún campo no es válido. El mensaje indica cuál. - 404
not_foundEl recurso no existe o es de otro equipo.
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"
}
#Obtener un borrador
GET/inboxes/{id}/drafts/{draft_id}
Devuelve el borrador con el texto, el HTML y la lista de attachments (sin el contenido de los archivos). created_by es la persona que lo escribió, o null cuando fue el agente de IA (source: "agent").
Parámetros de ruta
idstringobligatorioID del buzón de entrada.draft_idstringobligatorioID del borrador.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo.
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"
}
#Actualizar un borrador
PATCH/inboxes/{id}/drafts/{draft_id}
Acepta los mismos campos que la creación, salvo thread_id. Con attachments, la lista de adjuntos se sustituye: envía {"id"} para conservar un adjunto que el borrador ya tiene y filename con content para añadir otro (consulta Adjuntos).
Parámetros de ruta
idstringobligatorioID del buzón de entrada.draft_idstringobligatorioID del borrador.
Cuerpo de la solicitud
tostring[]Destinatários.ccstring[]Con copia.bccstring[]Con copia oculta.subjectstringAssunto.textstringTexto.htmlstringHTML.attachmentsobject[]Adjuntos del borrador, hasta 10 archivos y 40 MB en total. Consulta Adjuntos.filenamestringNombre del archivo, hasta 255 caracteres. Obligatorio sinid.contentstringContenido del archivo en Base64. Obligatorio sinid.content_typestringTipo MIME. Si se omite, se deduce de la extensión.idstringSolo al actualizar: ID de un adjunto que el borrador ya tiene, para conservarlo (sin reenviar el contenido).
Errores
- 422
validation_errorAlgún campo no es válido. El mensaje indica cuál. - 404
not_foundEl recurso no existe o es de otro equipo.
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 un borrador
POST/inboxes/{id}/drafts/{draft_id}/send
Envía el borrador (como respuesta, si pertenece a una conversación; como mensaje nuevo, si no) con sus adjuntos y lo elimina, junto con sus archivos.
Parámetros de ruta
idstringobligatorioID del buzón de entrada.draft_idstringobligatorioID del borrador.
Errores
- 422
validation_errorAlgún campo no es válido. El mensaje indica cuál. - 404
not_foundEl recurso no existe o es de otro equipo. - 403
validation_errorEl dominio del buzón de entrada no está verificado. - 403
sending_pausedLos envíos del equipo están en pausa. - 429
daily_quota_exceededLa cuota de envío del plan se agotó (tambiénmonthly_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…"
}
]
}
#Eliminar un borrador
DELETE/inboxes/{id}/drafts/{draft_id}
Parámetros de ruta
idstringobligatorioID del buzón de entrada.draft_idstringobligatorioID del borrador.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo.
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
Con el agente activado (agent.enabled en PATCH /inboxes/{id}), una IA lee cada correo nuevo, sigue tus instructions y el tone, y solo puede hacer lo que figure en actions: sugerir una respuesta (draft_reply), aplicar etiquetas y crear las que falten (add_labels), asignar (assign_thread), archivar (archive), marcar como spam (mark_as_spam), mover a la papelera (move_to_trash) y reenviar (forward_thread).
- Nunca envía nada por su cuenta: las respuestas se convierten en borradores (
source: "agent") para que una persona los revise y los envíe. - Solo reenvía a direcciones escritas en las instrucciones.
- Usa 1 crédito de IA por correo. No hace nada sin créditos, con el buzón de entrada o el agente desactivados, ni con correos automáticos (
Auto-Submitted) o enviados por el propio buzón de entrada. - Para escribir buenas
instructions, usaPOST /inboxes/{id}/agent/improve-instructions, que reescribe tu borrador con más claridad.
#Mejorar las instrucciones del agente
POST/inboxes/{id}/agent/improve-instructions
Reescribe el borrador de las instrucciones del agente para que sea más claro y específico, en el mismo idioma y con la misma intención, sin inventar datos: las direcciones de correo, los precios, los horarios y las reglas quedan como tú los escribiste. No guarda nada: guarda el resultado con PATCH /inboxes/{id} (agent.instructions) después de revisarlo.
Es asíncrono, como las demás solicitudes de IA: la respuesta es una tarea (ai_task) y el resultado llega por GET /ai/tasks/{id} como {"object": "agent_instructions", "instructions", "task_id", "status": "completed", "credits"}. Usa 1 crédito de IA.
Parámetros de ruta
idstringobligatorioID del buzón de entrada.
Cuerpo de la solicitud
instructionsstringobligatorioEl borrador de las instrucciones, de 1 a 4.000 caracteres.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo. - 422
validation_errorCampo ausente o fuera de los límites. - 429
ai_credits_exhaustedSe acabaron los créditos de IA del mes. Se renuevan el día 1 (UTC). - 503
ai_not_configuredEl asistente de IA no está disponible.
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 del buzón de entrada
Además de email.received, cuatro eventos acompañan a las conversaciones. Suscríbete a ellos en POST /webhooks; los formatos están en Webhooks.
| Evento | Cuándo |
|---|---|
inbox.thread.created | Se abrió una conversación nueva, por un correo recibido o por un mensaje enviado desde el buzón de entrada. |
inbox.thread.updated | Cambiaron la carpeta, el estado de leída, el responsable o las etiquetas (por PATCH, acción por lotes, envío a la papelera o por el agente), o un correo nuevo sacó la conversación del archivo. Un evento por cambio, y uno por conversación modificada en las acciones por lotes. |
inbox.thread.deleted | La conversación se eliminó definitivamente. |
inbox.draft.created | El agente de IA creó un borrador (source: "agent"). Los borradores creados por personas no generan evento. |
#Límites del plan
El plan define cuántos buzones de entrada tiene el equipo y qué funciones de ellos puede usar. Superar un límite responde 403 plan_limit_reached, con un mensaje que indica lo que falta. Los valores de cada plan están en Límites y en GET /plans.
| Recurso del plan | Clave | Qué cambia en la API |
|---|---|---|
| Buzones de entrada | inboxes | Cuántos buzones de entrada puede tener el equipo. Por encima de ese número, POST /inboxes responde 403. |
| Buzones de entrada en tu dominio | custom_inbound_domains | Sin esta función, solo hay buzones de entrada en in.postal.so: crear uno con domain_id responde 403, y el dominio no puede activar la recepción. |
| Agente de IA del buzón de entrada | inbox_agent | Activar agent.enabled en PATCH /inboxes/{id} responde 403 sin ella. |
| Créditos de IA | ai_credits | El agente y la traducción usan créditos cada mes; sin créditos, la traducción responde 429 ai_credits_exhausted y el agente no actúa. |
Si el plan se reduce y el equipo se queda con más buzones de entrada que el nuevo límite, Postal no elimina ninguno: los más antiguos, hasta el límite, siguen activos y los más nuevos quedan pausados (paused_by_plan: true en el buzón). Un buzón pausado sigue en la lista y se puede leer, pero PATCH /inboxes/{id} responde 403 paused_by_plan y su agente de IA no actúa. Vuelve a la normalidad cuando el plan lo permite de nuevo o cuando eliminas otros buzones. Consulta también Límites del plan en Correos.