E-mails
Envía, lista, programa y cancela correos electrónicos, con adjuntos, etiquetas, encabezados y plantillas.
Envía correos electrónicos, sigue el estado de cada mensaje, programa, cancela y consulta adjuntos. Los envíos por API y por SMTP aparecen en los mismos endpoints.
Quien recibe un correo enviado por aquí (en to y cc) queda guardado en la audiencia como contacto sin confirmar, que no recibe campañas ni automatizaciones hasta que lo confirmes. Se puede desactivar en Configuración › Equipo.
#Enviar un correo electrónico
POST/emails
Pone un correo electrónico en la cola de envío y devuelve el ID. El envío ocurre en segundos; sigue el resultado con GET /emails/{id} o con webhooks. Antes de enviar, Postal elimina los destinatarios que están en la lista de supresión. Para probar sin enviar de verdad, usa las direcciones de prueba.
Cabeceras
Idempotency-KeystringClave de 1 a 256 caracteres para repetir la solicitud sin duplicar el envío. Consulta Idempotencia.
Cuerpo de la solicitud
fromstringobligatorioRemitente, con el formato[email protected]oNome <[email protected]>. El dominio debe estar verificado y con envío activo. Hasta 998 caracteres.tostring | string[]obligatorioDestinatarios. Una cadena o una lista de hasta 50 direcciones. Se puede omitir cuando el destinatario es un contacto: consultauser_id,external_idycontact_id.user_idstringAlternativa ato: el ID de la persona en tu sistema, elexternal_idde un contacto (user_ides un alias). El correo va a la dirección de ese contacto. Se ignora cuando se envíato.external_idstringLo mismo queuser_id. Si se envían los dos, valeuser_id.contact_idstringAlternativa ato: el ID del contacto en Postal (o su dirección de correo). Vale después deuser_idyexternal_id.subjectstringobligatorioAsunto, de hasta 998 caracteres.htmlstringCuerpo en HTML. Obligatorio cuando no haytext.textstringVersión en texto plano. Recomendada incluso conhtml: mejora la entregabilidad.ccstring | string[]Cópia.bccstring | string[]Copia oculta.reply_tostring | string[]Direcciones de respuesta (Reply-To).headersobjectEncabezados adicionales, como{"List-Unsubscribe": "<https://…>"}. Valores de hasta 998 caracteres y sin saltos de línea. No se pueden sobrescribirFrom,To,Cc,Bcc,Subject,Reply-To,Sender,Date,Message-ID,Return-Path,MIME-Version,Content-Type,Content-Transfer-EncodingniDKIM-Signature. Para enviar un código de un solo uso que el cliente de correo pueda rellenar, usaOTP-Token: consulta Encabezado OTP-Token. Cuando elOTP-Tokenllega en la solicitud, la API comprueba el formato y responde422si el valor no es válido o si el encabezado aparece más de una vez, con cualquier combinación de mayúsculas y minúsculas (lo que valida Postal).tagsobject[]Hasta 50 etiquetas para filtrar e identificar el envío en los webhooks.namestringobligatorioLetras ASCII, números,_o-, hasta 256 caracteres. No puede empezar porpostal_.valuestringMismas reglas que el nombre. Opcional.
attachmentsobject[]Adjuntos, de hasta 40 MB en total. No se aceptan en lote. El plan puede permitir menos (o ningún adjunto): por encima del límite del plan, la respuesta es403 plan_limit_reached. Unpathque Postal no puede descargar en el momento del envío deja el correo con estadofailed.contentstringContenido del archivo en Base64. Obligatorio sinpath.pathstringURL http(s) pública desde la que Postal descarga el archivo en el momento del envío. Obligatorio sincontent.filenamestringNombre del archivo (hasta 255 caracteres). Conpath, por defecto es el nombre que aparece al final de la URL.content_typestringTipo MIME, comoapplication/pdf. Sin él, el tipo se deduce por la extensión.content_idstringPara imágenes incrustadas: usa<img src="cid:logo">en el HTML y"content_id": "logo"aquí.
scheduled_atstringFecha y hora del envío en ISO 8601 (p. ej.:2026-10-25T09:00:00-03:00). Debe estar en el futuro y a un máximo de 30 días. No se acepta en lote. Depende del recurso de programación del plan (403 plan_limit_reachedsi no lo incluye).templateobjectidstringobligatorioID o alias de la plantilla.variablesobjectValores de las variables, como{"NAME": "Ana"}.
Envío con plantilla
Con template, Postal usa la última versión publicada: el from, el subject y el reply_to de la plantilla valen cuando no vienen en el cuerpo, y las variables {{{NAME}}} se rellenan con variables (en el HTML, los valores se escapan). Una variable sin valor y sin valor por defecto genera 422, y lo mismo ocurre con un valor que no sea texto ni número o, en una variable de tipo number, que no sea numérico. También se rellenan los valores de variables que la plantilla no declara. No envíes html ni text junto con template. Una plantilla pausada por el plan responde 403 paused_by_plan.
{
"from": "Loja Exemplo <[email protected]>",
"to": "[email protected]",
"template": {
"id": "order-shipped",
"variables": {
"NAME": "Ana",
"ORDER": "10482"
}
}
}
Adjuntos e imágenes incrustadas
Envía el archivo en Base64 en content o una URL en path. Para una imagen dentro del HTML, usa el mismo content_id en el atributo src="cid:…".
{
"from": "Financeiro <[email protected]>",
"to": ["[email protected]"],
"subject": "Sua nota fiscal",
"html": "<img src=\"cid:logo\" alt=\"Loja Exemplo\"><p>Segue a nota fiscal do pedido #10482.</p>",
"attachments": [
{
"filename": "invoice-10482.pdf",
"content": "JVBERi0xLjcKJcfsj6IKNSAwIG9iago8PC9MZW5ndGggNiAwIFI+PgpzdHJlYW0K…"
},
{
"path": "https://cdn.lojaexemplo.com.br/logo.png",
"content_id": "logo"
}
]
}
Enviar a un contacto
Si guardas a las personas como contactos, no necesitas repetir la dirección: envía el user_id (el external_id del contacto) en lugar de to y Postal usa su correo. También vale en el lote. Un contacto que no existe devuelve 404. La lista de supresión sigue vigente: si la dirección está en ella, el correo no sale.
{
"from": "Loja Exemplo <[email protected]>",
"user_id": "user_42",
"template": {
"id": "order-shipped",
"variables": {
"NAME": "Ana",
"ORDER": "10482"
}
}
}
Programación
Con scheduled_at, el correo queda con estado scheduled hasta la hora indicada. Puedes reprogramarlo o cancelarlo mientras no haya salido. La cuota se cuenta cuando se crea el correo, y un evento email.scheduled entra en el historial.
Errores
- 422
validation_errorAlgún campo no es válido. El mensaje indica cuál. - 403
validation_errorEl dominio delfromno está verificado o no es de tu equipo. - 403
restricted_api_keyLa clave solo puede enviar desde otro dominio. - 403
sending_pausedLos envíos del equipo están pausados por una tasa alta de rebotes o quejas. - 403
paused_by_planEl dominio delfrom, latemplateo la propia clave está pausada porque supera el límite del plan. Consulta Límites del plan. - 403
plan_limit_reachedEl plan no incluye lo que pide el envío:scheduled_at, direcciones de prueba o adjuntos (o el total supera el límite del plan). - 404
not_foundLatemplateno existe o no se ha publicado. - 404
not_foundNingún contacto tiene eluser_id, elexternal_ido elcontact_idindicado. - 400
invalid_idempotency_keyLaIdempotency-Keyestá vacía o tiene más de 256 caracteres. - 409
concurrent_idempotent_requestsOtra solicitud con la mismaIdempotency-Keysigue en curso. - 409
invalid_idempotent_requestLa mismaIdempotency-Keyse usó con otro cuerpo. - 429
daily_quota_exceededSe agotó la cuota diaria: el límite diario del plan o el tope de la reputación de la cuenta, el que sea menor. Se restablece a las 00:00 UTC. Cuando es la reputación la que limita, el mensaje indica el nivel y el tope. - 429
monthly_quota_exceededLa cuota mensual del plan se agotó, y la reserva del Adicional de envíos también, si el equipo tiene una. El mensaje dice qué hacer, incluido dónde comprar el adicional cuando el plan lo ofrece.
cURL
curl -X POST https://api.postal.so/emails \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Idempotency-Key: order-10482-shipped" \
-H "Content-Type: application/json" \
-d '{
"from": "Loja Exemplo <[email protected]>",
"to": ["[email protected]"],
"subject": "Seu pedido #10482 saiu para entrega",
"html": "<p>O pedido <b>#10482</b> está a caminho.</p>",
"text": "O pedido #10482 está a caminho.",
"tags": [
{
"name": "category",
"value": "shipping"
}
]
}'
Node.js
const response = await fetch('https://api.postal.so/emails', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'order-10482-shipped',
},
body: JSON.stringify({
from: 'Loja Exemplo <[email protected]>',
to: ['[email protected]'],
subject: 'Seu pedido #10482 saiu para entrega',
html: '<p>O pedido <b>#10482</b> está a caminho.</p>',
text: 'O pedido #10482 está a caminho.',
tags: [
{
name: 'category',
value: 'shipping',
},
],
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/emails",
headers={
"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}",
"Idempotency-Key": "order-10482-shipped",
},
json={
"from": "Loja Exemplo <[email protected]>",
"to": ["[email protected]"],
"subject": "Seu pedido #10482 saiu para entrega",
"html": "<p>O pedido <b>#10482</b> está a caminho.</p>",
"text": "O pedido #10482 está a caminho.",
"tags": [
{
"name": "category",
"value": "shipping",
},
],
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('emails', [
'headers' => [
'Authorization' => 'Bearer '.getenv('POSTAL_API_KEY'),
'Idempotency-Key' => 'order-10482-shipped',
],
'json' => [
'from' => 'Loja Exemplo <[email protected]>',
'to' => ['[email protected]'],
'subject' => 'Seu pedido #10482 saiu para entrega',
'html' => '<p>O pedido <b>#10482</b> está a caminho.</p>',
'text' => 'O pedido #10482 está a caminho.',
'tags' => [
[
'name' => 'category',
'value' => 'shipping',
],
],
],
]);
$data = json_decode($response->getBody(), true);
{
"id": "019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b"
}
#Enviar en lote
POST/emails/batch
Envía hasta 100 correos electrónicos en una solicitud. El cuerpo es una lista de objetos con los mismos campos de POST /emails, excepto attachments y scheduled_at. Todo se valida antes: o todos los correos entran en la cola, o ninguno. Los errores de validación indican la posición, como [3] Missing `subject` field., y la cuota se comprueba para el lote entero (los elementos solo para direcciones de prueba no cuentan). El envío en lote es un recurso del plan.
Cuerpo de la solicitud (lista)
[ ]object[]obligatorioDe 1 a 100 correos, cada uno confrom,to,subject,htmlotexty, si quieres,cc,bcc,reply_to,headers,tagsytemplate. En lugar deto, valenuser_id,external_idocontact_id, como enPOST /emails.
Errores
- 422
validation_errorEl cuerpo no es una lista, tiene más de 100 elementos, o algún elemento no es válido (con la posición en el mensaje). - 403
validation_errorAlgúnfromusa un dominio no verificado. - 403
plan_limit_reachedEl plan no incluye el envío en lote (ni las direcciones de prueba, si algún elemento las usa). - 403
sending_pausedLos envíos del equipo están pausados por una tasa alta de rebotes o quejas. - 403
paused_by_planAlgúnfromusa un dominio pausado por el plan, o algún elemento usa una plantilla pausada. - 404
not_foundAlgún elemento usa unatemplateinexistente o no publicada, o un contacto que no existe (el mensaje indica la posición). - 429
daily_quota_exceededEl lote superaría la cuota diaria o mensual.
cURL
curl -X POST https://api.postal.so/emails/batch \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '[
{
"from": "Loja Exemplo <[email protected]>",
"to": ["[email protected]"],
"subject": "Seu cupom chegou",
"html": "<p>Olá, Ana! Use o cupom <b>BEMVINDA</b>.</p>"
},
{
"from": "Loja Exemplo <[email protected]>",
"to": ["[email protected]"],
"subject": "Seu cupom chegou",
"html": "<p>Olá, Bruno! Use o cupom <b>BEMVINDO</b>.</p>"
}
]'
Node.js
const response = await fetch('https://api.postal.so/emails/batch', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify([
{
from: 'Loja Exemplo <[email protected]>',
to: ['[email protected]'],
subject: 'Seu cupom chegou',
html: '<p>Olá, Ana! Use o cupom <b>BEMVINDA</b>.</p>',
},
{
from: 'Loja Exemplo <[email protected]>',
to: ['[email protected]'],
subject: 'Seu cupom chegou',
html: '<p>Olá, Bruno! Use o cupom <b>BEMVINDO</b>.</p>',
},
]),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/emails/batch",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json=[
{
"from": "Loja Exemplo <[email protected]>",
"to": ["[email protected]"],
"subject": "Seu cupom chegou",
"html": "<p>Olá, Ana! Use o cupom <b>BEMVINDA</b>.</p>",
},
{
"from": "Loja Exemplo <[email protected]>",
"to": ["[email protected]"],
"subject": "Seu cupom chegou",
"html": "<p>Olá, Bruno! Use o cupom <b>BEMVINDO</b>.</p>",
},
],
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('emails/batch', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
[
'from' => 'Loja Exemplo <[email protected]>',
'to' => ['[email protected]'],
'subject' => 'Seu cupom chegou',
'html' => '<p>Olá, Ana! Use o cupom <b>BEMVINDA</b>.</p>',
],
[
'from' => 'Loja Exemplo <[email protected]>',
'to' => ['[email protected]'],
'subject' => 'Seu cupom chegou',
'html' => '<p>Olá, Bruno! Use o cupom <b>BEMVINDO</b>.</p>',
],
],
]);
$data = json_decode($response->getBody(), true);
{
"data": [
{
"id": "019a1b2c-51aa-7c21-9f02-6b7c8d9e0f1a"
},
{
"id": "019a1b2c-51ab-7d30-8e11-2a3b4c5d6e7f"
}
]
}
#Listar correos electrónicos
GET/emails
Lista los correos electrónicos enviados, del más reciente al más antiguo, con paginación por cursor. La lista no incluye los eventos; usa GET /emails/{id} para ver el historial.
Parámetros de consulta
limitintegerElementos por página, de 1 a 100. Por defecto: 20.afterstringID del último elemento recibido, para la página siguiente.beforestringID del primer elemento recibido, para la página anterior.searchstringBusca en remitente, asunto y destinatarios. Un ID completo encuentra el correo exacto. Los destinatarios incluyenccybcc; la búsqueda admite hasta 200 caracteres.statusstringUno o más estados separados por comas, comobounced,complained. Consulta los estados. Un valor que no es un estado devuelve422.domain_idstringSolo correos de un dominio.api_key_idstringSolo correos enviados con una clave.start_datestringCreados a partir de esta fecha (ISO 8601). Una fecha sin hora, como2026-10-01, empieza a la medianoche UTC.end_datestringCreados hasta esta fecha (ISO 8601). Una fecha sin hora, como2026-10-24, vale hasta el final de ese día (UTC). Sistart_datees posterior aend_date, la respuesta es422.
Errores
- 422
validation_errorFiltro o cursor no válido, oafterybeforejuntos. También cuandostart_datees posterior aend_date.
cURL
curl 'https://api.postal.so/emails?limit=2&status=delivered,opened' \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/emails?limit=2&status=delivered,opened', {
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",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
params={
"limit": 2,
"status": "delivered,opened",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->get('emails', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'query' => [
'limit' => 2,
'status' => 'delivered,opened',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "list",
"has_more": true,
"data": [
{
"object": "email",
"id": "019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b",
"message_id": "<0103019a1b2c4d5e-6f7a8b9c-1d2e-4f3a-9b8c-7d6e5f4a3b2c-000000@sa-east-1.amazonses.com>",
"to": ["[email protected]"],
"from": "Loja Exemplo <[email protected]>",
"created_at": "2026-10-24T13:02:11.000000Z",
"subject": "Seu pedido #10482 saiu para entrega",
"html": "<p>O pedido <b>#10482</b> está a caminho.</p>",
"text": "O pedido #10482 está a caminho.",
"bcc": [],
"cc": [],
"reply_to": ["[email protected]"],
"last_event": "delivered",
"scheduled_at": null,
"simulated": false,
"tags": [
{
"name": "category",
"value": "shipping"
}
]
}
]
}
#Obtener un correo electrónico
GET/emails/{id}
Devuelve el correo con el contenido, el estado actual (last_event) y la lista de eventos. En los correos a las direcciones de prueba, simulated es true. También indica de dónde vino: source es api o smtp, y log_id apunta al registro de la solicitud. Los campos están en El objeto correo electrónico y los tipos de evento en Eventos de un correo electrónico.
Qué claves pueden llamar: una clave de acceso total (o el panel) lee cualquier correo del equipo. Una clave sending_access lee solo los correos que ella misma envió, por API o por SMTP, y así sigue el estado de la entrega sin webhooks. Cualquier otro correo del equipo (de otra clave, del panel, de campañas o de automatizaciones) responde 404 not_found, igual que un correo que no existe. Las demás rutas de /emails siguen exigiendo acceso total.
Parámetros de ruta
idstringobligatorioID del correo.
Errores
- 404
not_foundEl correo no existe, es de otro equipo o, con una clavesending_access, no lo envió ella.
cURL
curl https://api.postal.so/emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b', {
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/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b",
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/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "email",
"id": "019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b",
"message_id": "<0103019a1b2c4d5e-6f7a8b9c-1d2e-4f3a-9b8c-7d6e5f4a3b2c-000000@sa-east-1.amazonses.com>",
"to": ["[email protected]"],
"from": "Loja Exemplo <[email protected]>",
"created_at": "2026-10-24T13:02:11.000000Z",
"subject": "Seu pedido #10482 saiu para entrega",
"html": "<p>O pedido <b>#10482</b> está a caminho.</p>",
"text": "O pedido #10482 está a caminho.",
"bcc": [],
"cc": [],
"reply_to": ["[email protected]"],
"last_event": "delivered",
"scheduled_at": null,
"simulated": false,
"tags": [
{
"name": "category",
"value": "shipping"
}
],
"events": [
{
"type": "email.sent",
"occurred_at": "2026-10-24T13:02:12.000000Z",
"data": null
},
{
"type": "email.delivered",
"occurred_at": "2026-10-24T13:02:14.000000Z",
"data": {
"recipients": ["[email protected]"]
}
}
],
"domain_id": "0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f",
"api_key_id": "0199f0e2-7b8c-7d9e-8f0a-1b2c3d4e5f6a",
"template_id": null,
"source": "api",
"log_id": "019a1b2c-4d60-7a11-b2c3-d4e5f6a7b8c9"
}
#Reprogramar un correo electrónico
PATCH/emails/{id}
Cambia la fecha de envío de un correo programado. Solo funciona mientras el estado sea scheduled.
Parámetros de ruta
idstringobligatorioID del correo.
Cuerpo de la solicitud
scheduled_atstringobligatorioNueva fecha en ISO 8601, en el futuro y a un máximo de 30 días.
Errores
- 422
validation_errorEl correo no está programado o la fecha no es válida. - 404
not_foundEl recurso no existe o es de otro equipo.
cURL
curl -X PATCH https://api.postal.so/emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"scheduled_at": "2026-10-26T09:00:00-03:00"
}'
Node.js
const response = await fetch('https://api.postal.so/emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
scheduled_at: '2026-10-26T09:00:00-03:00',
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.patch(
"https://api.postal.so/emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"scheduled_at": "2026-10-26T09:00:00-03:00",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->patch('emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'scheduled_at' => '2026-10-26T09:00:00-03:00',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "email",
"id": "019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b"
}
#Cancelar un correo electrónico programado
POST/emails/{id}/cancel
Cancela el envío de un correo programado. El estado pasa a canceled y un evento email.canceled entra en el historial.
Parámetros de ruta
idstringobligatorioID del correo.
Errores
- 422
validation_errorEl correo no está programado (ya salió o ya se canceló). - 404
not_foundEl recurso no existe o es de otro equipo.
cURL
curl -X POST https://api.postal.so/emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/cancel \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/cancel', {
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/emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/cancel",
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('emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/cancel', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "email",
"id": "019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b"
}
#Mensaje original
GET/emails/{id}/raw
Devuelve el mensaje tal como se envió, en formato RFC 5322 (text/plain): el original, en los envíos por SMTP, o reconstruido, en los envíos por API.
Parámetros de ruta
idstringobligatorioID del correo.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo.
cURL
curl https://api.postal.so/emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/raw \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/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/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/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/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/raw', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$message = (string) $response->getBody();
From: Loja Exemplo <[email protected]>
To: [email protected]
Subject: Seu pedido #10482 saiu para entrega
MIME-Version: 1.0
Content-Type: multipart/alternative; boundary="b1_Xk2p"
--b1_Xk2p
Content-Type: text/plain; charset=utf-8
O pedido #10482 está a caminho.
--b1_Xk2p--
#Diagnóstico de entregabilidad
GET/emails/{id}/insights
Ejecuta comprobaciones de buenas prácticas en el correo e indica si cada una se superó. Los códigos son: has_text_version, no_link_shorteners, https_links, no_ip_links, images_have_alt, html_under_gmail_limit (HTML de hasta 102 KB), enough_text, reply_friendly_sender, subject_not_all_caps, unsubscribe_option y dmarc_configured (solo cuando el DNS responde). La IA explica cómo corregir los que fallaron.
Parámetros de ruta
idstringobligatorioID del correo.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo.
cURL
curl https://api.postal.so/emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/insights \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/insights', {
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/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/insights",
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/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/insights', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "list",
"data": [
{
"code": "has_text_version",
"passed": true,
"data": {}
},
{
"code": "https_links",
"passed": false,
"data": {
"count": 1
}
},
{
"code": "dmarc_configured",
"passed": true,
"data": {
"domain": "lojaexemplo.com.br"
}
}
]
}
#Crear un enlace de visualización
POST/emails/{id}/share
Crea un enlace público y temporal para ver el correo en el navegador, útil para mostrarlo a alguien sin acceso al panel. El HTML se abre aislado, sin scripts.
Parámetros de ruta
idstringobligatorioID del correo.
Cuerpo de la solicitud
expires_instringValidez, de 1 minuto a 48 horas:10m,2 hours,1 day… Por defecto:48h.
Errores
- 422
validation_errorexpires_incon un formato o intervalo no válido. - 403
plan_limit_reachedEl plan no incluye los enlaces para compartir correos. - 404
not_foundEl recurso no existe o es de otro equipo.
cURL
curl -X POST https://api.postal.so/emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/share \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"expires_in": "2h"
}'
Node.js
const response = await fetch('https://api.postal.so/emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/share', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
expires_in: '2h',
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/share",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"expires_in": "2h",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/share', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'expires_in' => '2h',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "email",
"id": "019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b",
"url": "https://api.postal.so/shared/emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b?expires=1761321731&signature=4f1c…"
}
#Listar adjuntos
GET/emails/{id}/attachments
Lista los adjuntos de un correo enviado. Cada uno incluye un download_url firmado, válido durante 1 hora. En los adjuntos enviados por path, el enlace es la URL original y expires_at llega nulo. Acepta limit, after y before, como las demás listas.
Parámetros de ruta
idstringobligatorioID del correo.
Errores
- 422
validation_errorlimitfuera del intervalo oafterybeforejuntos. - 404
not_foundEl recurso no existe o es de otro equipo.
cURL
curl https://api.postal.so/emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/attachments \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/attachments', {
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/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/attachments",
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/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/attachments', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "list",
"has_more": false,
"data": [
{
"id": "019a1b2c-4d5f-7e70-9b8a-7c6d5e4f3a2b",
"filename": "invoice-10482.pdf",
"content_type": "application/pdf",
"content_id": null,
"content_disposition": "attachment",
"download_url": "https://api.postal.so/attachments/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/019a1b2c-4d5f-7e70-9b8a-7c6d5e4f3a2b?expires=1761314531&signature=9a7e…",
"expires_at": "2026-10-24T14:02:11.000Z",
"size": 48213
}
]
}
#Obtener un adjunto
GET/emails/{id}/attachments/{attachment_id}
Devuelve un adjunto con el enlace de descarga.
Parámetros de ruta
idstringobligatorioID del correo.attachment_idstringobligatorioID del adjunto.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo.
cURL
curl https://api.postal.so/emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/attachments/019a1b2c-4d5f-7e70-9b8a-7c6d5e4f3a2b \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/attachments/019a1b2c-4d5f-7e70-9b8a-7c6d5e4f3a2b', {
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/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/attachments/019a1b2c-4d5f-7e70-9b8a-7c6d5e4f3a2b",
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/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/attachments/019a1b2c-4d5f-7e70-9b8a-7c6d5e4f3a2b', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "attachment",
"id": "019a1b2c-4d5f-7e70-9b8a-7c6d5e4f3a2b",
"filename": "invoice-10482.pdf",
"content_type": "application/pdf",
"content_id": null,
"content_disposition": "attachment",
"download_url": "https://api.postal.so/attachments/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b/019a1b2c-4d5f-7e70-9b8a-7c6d5e4f3a2b?expires=1761314531&signature=9a7e…",
"expires_at": "2026-10-24T14:02:11.000Z",
"size": 48213
}
#Estado de un correo electrónico
El campo last_event muestra el estado más avanzado del correo. Como los eventos pueden llegar fuera de orden, el estado solo avanza: un correo clicked no vuelve a delivered.
| Estado | Significado |
|---|---|
queued | En la cola, saliendo en instantes. |
scheduled | Programado para scheduled_at. |
sent | Aceptado por el proveedor de envío. |
delivery_delayed | El servidor del destinatario aplazó la entrega; los reintentos se hacen solos. |
delivered | Entregado al servidor del destinatario. |
opened | Abierto (con el seguimiento de aperturas activado en el dominio). |
clicked | Se hizo clic en un enlace (con el seguimiento de clics activado). |
bounced | Devuelto. Los rebotes permanentes pasan a la lista de supresión. |
complained | El destinatario lo marcó como spam. La dirección pasa a la lista de supresión. |
failed | No se pudo enviar (dominio no verificado, envío pausado, adjunto inaccesible…). |
suppressed | Todos los destinatarios, o parte de ellos, estaban en la lista de supresión. |
canceled | Programación cancelada. |
#El objeto correo electrónico
La lista (GET /emails) y la consulta de un correo (GET /emails/{id}) devuelven el mismo objeto. La consulta añade el historial y el origen. El envío (POST /emails) devuelve solo el id, y el envío en lote, una lista de id. Postal guarda los correos, los adjuntos y el historial durante 30 días (y los programados hasta que salgan); pasado ese tiempo, la consulta responde 404. Consulta Límites.
| Campo | Tipo | Descripción |
|---|---|---|
object, id | string | email y el ID del correo. |
message_id | string | null | El Message-ID del correo. Es null hasta que el proveedor de envío lo acepta (correos en cola, programados o cancelados). |
from, to, cc, bcc, reply_to | string | string[] | Las direcciones, tal como se enviaron. cc, bcc y reply_to son listas vacías cuando no existen. |
subject, html, text | string | null | El contenido enviado. En un envío con plantilla, es el contenido ya rellenado con las variables. |
created_at | string | Cuándo se creó el correo (ISO 8601, UTC). |
last_event | string | El estado actual: queued, scheduled, sent, delivery_delayed, delivered, opened, clicked, bounced, failed, suppressed, canceled o complained. |
scheduled_at | string | null | La fecha de envío indicada con scheduled_at, o null en un envío inmediato. |
simulated | boolean | true en los correos a las direcciones de prueba, que nunca se envían de verdad. |
tags | object[] | Las etiquetas, como [{"name", "value"}]. |
events | object[] | Solo en GET /emails/{id}: el historial, del más antiguo al más reciente, como [{"type", "occurred_at", "data"}]. Consulta Eventos de un correo electrónico. |
domain_id, api_key_id, template_id | string | null | Solo en GET /emails/{id}: el dominio del remitente, la clave de API usada en el envío y la plantilla usada (null cuando no hubo clave o plantilla). |
source, log_id | string | Solo en GET /emails/{id}: api o smtp, y el ID del registro de la solicitud (null si no hay). |
#Eventos de un correo electrónico
El campo events de GET /emails/{id} guarda todo lo que le ocurrió al correo, en orden. Cada evento tiene type, occurred_at y data (o null). Casi todos también se pueden suscribir como eventos de webhook (email.canceled y email.unsubscribed existen solo en el historial). Los eventos que involucran a varios destinatarios listan, en data.recipients, las direcciones a las que se aplican.
| Tipo | Cuándo | data |
|---|---|---|
email.scheduled | El correo se creó con scheduled_at. | null |
email.canceled | La programación se canceló. | null |
email.sent | El proveedor de envío aceptó el correo. | null |
email.delivered | El servidor del destinatario recibió el correo. | recipients |
email.delivery_delayed | La entrega se aplazó; los reintentos se hacen solos. | delay_type, recipients |
email.bounced | El correo rebotó. Los rebotes permanentes entran en la lista de supresión. | bounce (type, subType, message, diagnosticCode), recipients |
email.complained | El destinatario lo marcó como spam. La dirección entra en la lista de supresión. | recipients, feedback_type |
email.failed | No se pudo enviar (dominio sin verificar, envío pausado, adjunto inaccesible…). | reason |
email.suppressed | Todos los destinatarios, o parte de ellos, estaban en la lista de supresión y se descartaron en el envío. Los demás siguen con normalidad. | recipients |
email.opened | El destinatario abrió el correo (seguimiento de aperturas activado en el dominio). | open (ipAddress, userAgent, timestamp) |
email.clicked | Se hizo clic en un enlace (seguimiento de clics activado). | click (ipAddress, link, userAgent, timestamp) |
email.replied | Llegó una respuesta al correo de una campaña o automatización que detecta respuestas. | reply (from, subject, message_id, preview, attachments), auto |
email.unsubscribed | El contacto canceló su suscripción desde el enlace de un correo de campaña. | null |
El last_event solo avanza, pero la lista de events guarda todos los eventos, incluso los que llegan fuera de orden. email.replied y email.unsubscribed no cambian el last_event.
#Direcciones de prueba
Para probar la integración y los webhooks de extremo a extremo, sin enviar correos de verdad y sin arriesgar la reputación de tu dominio, envía a las direcciones de prueba de Postal, en el dominio sandbox.postal.so. Es la misma idea que [email protected] de Resend.
El correo se acepta como cualquier otro: la misma validación, el from de un dominio verificado del equipo, adjuntos, plantillas, etiquetas, scheduled_at, Idempotency-Key, el envío en lote y el SMTP. Aparece en GET /emails y en el panel, con el historial de eventos, pero nunca se envía: en lugar del proveedor, Postal registra los eventos unos segundos después, en el orden de un envío real, y los webhooks salen con normalidad.
| Dirección | Eventos, en orden | Estado final |
|---|---|---|
[email protected] | email.sent, email.delivered | delivered |
[email protected] | email.sent, email.bounced (rebote permanente: type Permanent, subType General, message smtp; 550 5.1.1 user unknown) | bounced |
[email protected] | email.sent, email.delivered, email.complained | complained |
[email protected] | email.suppressed (sin email.sent ni email.delivered) | suppressed |
[email protected] | email.sent, email.delivery_delayed | delivery_delayed |
Reglas
- Las mayúsculas y minúsculas no importan, y se puede añadir una etiqueta después de
+, de hasta 64 caracteres entre letras, números,.,_y-:[email protected]. Usa la etiqueta para reconocer cada prueba en los eventos. - No los mezcles. Los destinatarios de un correo (
to,ccybcc) son todos direcciones de prueba o todos reales; mezclarlos devuelve422 validation_error. Una dirección del dominio de prueba con otro nombre, como[email protected], también devuelve422, con la lista de los nombres válidos. - Se permiten varias direcciones de prueba en el mismo correo: cada resultado se convierte en su propio evento, con la lista
recipientsde las direcciones de ese resultado. El estado final sigue el evento más avanzado (complainedvale más quebouncedysuppressed, que valen más quedelivered). - No cuentan en nada que afecte a tu envío real: no usan la cuota de envío (diaria, mensual o de envío por encima de la cuota), no entran en la lista de supresión (un
bounced@ocomplained@no bloquea nada), no entran en las tasas de rebote y de queja que pueden pausar los envíos, no aparecen en las métricas ni en el resumen semanal, y no activan el aviso de primer correo. - Lo demás sigue vigente: el límite de solicitudes por segundo, las claves de API restringidas a un dominio y la pausa de envíos del equipo (
403 sending_paused). - Recurso del plan. Las direcciones de prueba dependen del recurso
test_addressesdel plan (consulta Límites del plan); sin él, el envío responde403 plan_limit_reached. - El correo trae
"simulated": trueenGET /emails/{id}. No hace falta configurar DNS: no se entrega nada a ese dominio.
{
"from": "Loja Exemplo <[email protected]>",
"to": ["[email protected]"],
"subject": "Seu pedido #10482 saiu para entrega",
"html": "<p>O pedido <b>#10482</b> está a caminho.</p>",
"tags": [
{
"name": "category",
"value": "shipping"
}
]
}
Unos segundos después, tu webhook recibe el mismo evento de un rebote real:
{
"type": "email.bounced",
"created_at": "2026-10-24T13:02:15.004Z",
"data": {
"email_id": "019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b",
"created_at": "2026-10-24T13:02:11.000Z",
"from": "Loja Exemplo <[email protected]>",
"to": ["[email protected]"],
"subject": "Seu pedido #10482 saiu para entrega",
"tags": {
"category": "shipping"
},
"bounce": {
"diagnosticCode": ["smtp; 550 5.1.1 user unknown"],
"message": "smtp; 550 5.1.1 user unknown",
"subType": "General",
"type": "Permanent"
}
}
}
#Límites del plan
Algunos recursos de envío dependen del plan. Cuando el plan no incluye uno de ellos, la API responde 403 plan_limit_reached y el mensaje indica qué recurso falta. Lo que incluye cada plan está en Límites y en GET /plans.
| Recurso del plan | Clave | Dónde vale |
|---|---|---|
| Envío en lote | batch_sending | POST /emails/batch |
| Programación de envíos | scheduled_sending | El campo scheduled_at de POST /emails. |
| Direcciones de prueba simuladas | test_addresses | sandbox.postal.so |
| Tamaño de los adjuntos | attachments_mb | El total de los attachments: 0 no permite adjuntos; un número limita el total en MB; sin número vale el límite de 40 MB de la plataforma. |
| Enlaces para compartir correos | shared_email_links | POST /emails/{id}/share |
| Envío por SMTP | smtp_access | SMTP |
Las cuotas de envío son otro límite: cuando se agotan, la respuesta es 429 daily_quota_exceeded o 429 monthly_quota_exceeded (consulta Cuotas). El límite diario es el menor entre el del plan y el tope de la reputación de la cuenta, que empieza en 500 correos por día en las cuentas nuevas. Antes de rechazar con monthly_quota_exceeded, Postal usa la reserva del Adicional de envíos, si el equipo tiene una; ella no levanta el límite diario. Los correos a direcciones de prueba no usan la cuota.
Envíos pausados
Cuando las tasas de rebote, de queja o de contenido rechazado del equipo superan el límite (mira Pausa automática), Postal pausa los envíos para proteger la reputación de tus dominios. Mientras tanto, POST /emails, POST /emails/batch y los envíos a direcciones de prueba responden 403 sending_paused. Los webhooks sending.paused y sending.resumed avisan cuando eso empieza y termina (consulta Webhooks).
Elementos pausados por el plan
Si el plan cambia y el equipo pasa a tener más dominios, plantillas, claves de API, buzones de entrada, webhooks u otros elementos de los que permite el nuevo plan, Postal no borra nada: los más antiguos, hasta el límite, siguen activos, y los más nuevos quedan pausados. Un elemento pausado sigue en la lista y se puede leer, y su campo paused_by_plan es true. Modificarlo o usarlo responde 403 paused_by_plan. En los envíos, esto vale para un from de dominio pausado, para una template pausada y para la propia clave de API, cuando es la pausada: toda solicitud hecha con ella se rechaza. Los elementos vuelven a estar activos en cuanto el plan lo permite de nuevo, o cuando eliminas otros elementos.
{
"statusCode": 403,
"name": "plan_limit_reached",
"message": "Your plan does not include “Scheduled sending”. Upgrade your plan to use it."
}
{
"statusCode": 403,
"name": "paused_by_plan",
"message": "This item is paused because it is above the limit of your current plan. Upgrade your plan or delete other items to use it again."
}
Los mensajes siguen el Accept-Language (sin él, llegan en inglés); el name no cambia.
#Con los SDK
Los SDK oficiales tienen métodos para enviar (emails.send y emails.batch), consultar y listar (emails.get, emails.list y emails.iterate), reprogramar y cancelar (emails.update y emails.cancel) y leer el mensaje original (emails.raw). Los campos tienen los nombres de la API.
Node.js
import { Postal } from 'postal-so'
const postal = new Postal() // lê POSTAL_API_KEY
const { id } = await postal.emails.send({
from: 'Loja Exemplo <[email protected]>',
to: '[email protected]',
subject: 'Seu pedido #10482 saiu para entrega',
html: '<p>O pedido <b>#10482</b> está a caminho.</p>',
scheduled_at: new Date(Date.now() + 3600_000),
})
const email = await postal.emails.get(id) // email.last_event e email.events
await postal.emails.update(id, { scheduled_at: '2026-10-26T09:00:00-03:00' })
await postal.emails.cancel(id)
PHP
use Postal\Client;
$postal = new Client(); // lê POSTAL_API_KEY
$result = $postal->emails->send([
'from' => 'Loja Exemplo <[email protected]>',
'to' => '[email protected]',
'subject' => 'Seu pedido #10482 saiu para entrega',
'html' => '<p>O pedido <b>#10482</b> está a caminho.</p>',
'scheduled_at' => new DateTimeImmutable('+1 hour'),
]);
$email = $postal->emails->get($result['id']); // last_event e events
$postal->emails->update($result['id'], '2026-10-26T09:00:00-03:00');
$postal->emails->cancel($result['id']);