Inbound
Recibe correos en tu app.
Cada mensaje que llega a una dirección tuya se convierte en un evento JSON en tu webhook, con remitente, asunto y adjuntos. Lee el texto, el HTML y los archivos por la API, sin montar un servidor de correo.
En el plan Gratis, recibe en una dirección de in.postal.so
{
"type": "email.received",
"data": {
"email_id": "019a1c00-3e4f-7a5b-…",
"from": "Bruno <[email protected]>",
"to": ["[email protected]"],
"subject": "Factura del pedido #10475",
"attachments": [{
"filename": "nota-fiscal-10475.pdf",
"content_type": "application/pdf",
"size": 48213
}]
}
}El correo como interfaz
El correo se convierte en una entrada de tu sistema
Quien escribe usa el correo de siempre. Tu app recibe cada mensaje como datos y decide qué hacer: abrir un ticket, guardar un adjunto, responder.
Cómo funciona
Configúralo en tres pasos
app.post('/webhooks/postal', express.raw({ type: 'application/json' }), async (req, res) => {
const event = verifyWebhook(req.body, req.headers, process.env.POSTAL_WEBHOOK_SECRET);
if (event.type === 'email.received') {
await cola.agregar(event.data.email_id); // procésalo fuera de la solicitud
}
res.sendStatus(200);
});Ejemplos de código
Del aviso al contenido, en pocas líneas
Comprueba la firma con el SDK oficial, obtén el mensaje completo y descarga los adjuntos.
Webhook en Node.js
Con el paquete postal-so, verifyWebhook comprueba la firma y devuelve el evento. El cuerpo debe llegar sin procesar, sin pasar por el parser de JSON.
- Rechaza avisos con más de 5 minutos de diferencia
- El mensaje completo viene de GET /emails/receiving/{id}
- Responde 2xx en hasta 15 segundos
import express from 'express';
import { verifyWebhook } from 'postal-so';
const app = express();
const api = { Authorization: `Bearer ${process.env.POSTAL_API_KEY}` };
// El cuerpo debe llegar sin procesar para que la firma coincida
app.post('/webhooks/postal', express.raw({ type: 'application/json' }), async (req, res) => {
let event;
try {
event = verifyWebhook(req.body, req.headers, process.env.POSTAL_WEBHOOK_SECRET);
} catch {
return res.sendStatus(400);
}
if (event.type === 'email.received') {
const { email_id, from, subject } = event.data;
const email = await fetch(`https://api.postal.so/emails/receiving/${email_id}`, { headers: api })
.then((response) => response.json());
// email.text, email.html y email.attachments[].download_url
await abrirTicket({ from, subject, texto: email.text, adjuntos: email.attachments });
}
res.sendStatus(200);
});Webhook en PHP
Con el paquete postal-so/postal-php, Webhook::verify comprueba la firma y devuelve el evento como array. Después, el contenido y los adjuntos vienen de la API.
- Funciona en PHP puro y en Laravel
- Cada adjunto tiene un enlace de descarga firmado
- El nombre del archivo puede venir vacío: usa el id como alternativa
use Postal\Exceptions\WebhookVerificationException;
use Postal\Webhook;
try {
$event = Webhook::verify(file_get_contents('php://input'), getallheaders(), getenv('POSTAL_WEBHOOK_SECRET'));
} catch (WebhookVerificationException $e) {
http_response_code(400);
exit;
}
if ($event['type'] === 'email.received') {
$id = $event['data']['email_id'];
$api = stream_context_create(['http' => ['header' => 'Authorization: Bearer ' . getenv('POSTAL_API_KEY')]]);
$email = json_decode(file_get_contents("https://api.postal.so/emails/receiving/{$id}", false, $api), true);
// $email['text'], $email['html'] y los adjuntos, con el enlace de descarga
foreach ($email['attachments'] as $adjunto) {
$nombre = basename($adjunto['filename'] ?? $adjunto['id']);
file_put_contents("/var/adjuntos/{$nombre}", file_get_contents($adjunto['download_url']));
}
}
http_response_code(200);Desde la línea de comandos
Para probar o para obtener mensajes sin webhook: el mensaje completo, un adjunto y el original, con todos los encabezados.
- Exige una clave de API de acceso total
- El enlace del adjunto vale 1 hora; vuelve a obtener el mensaje para otro
- Lista con filtros: inbox_id, unread y search
# El mensaje completo: texto, HTML, encabezados y adjuntos
curl https://api.postal.so/emails/receiving/019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d \
-H "Authorization: Bearer $POSTAL_API_KEY"
# Un adjunto: el download_url está firmado y vale 1 hora (no necesita la clave)
curl -o nota-fiscal-10475.pdf "$DOWNLOAD_URL"
# El mensaje original, tal como llegó al servidor
curl https://api.postal.so/emails/receiving/019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d/raw \
-H "Authorization: Bearer $POSTAL_API_KEY"Formato
Lo que llega y lo que obtienes
El webhook trae el resumen. La API trae el contenido completo, con los enlaces de los adjuntos.
{
"type": "email.received",
"created_at": "2026-11-04T12:41:09.880Z",
"data": {
"email_id": "019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d",
"inbox_id": "0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
"thread_id": "019a1c00-5a10-7d11-8e22-0f1a2b3c4d5e",
"created_at": "2026-11-04T12:41:09.000Z",
"from": "Bruno <[email protected]>",
"to": ["[email protected]"],
"cc": [],
"subject": "Factura del pedido #10475",
"message_id": "[email protected]",
"attachments": [
{
"id": "019a1c00-3e50-7b6c-8d7e-9f0a1b2c3d4e",
"filename": "nota-fiscal-10475.pdf",
"content_type": "application/pdf",
"size": 48213
}
]
}
}{
"object": "received_email",
"id": "019a1c00-3e4f-7a5b-9c6d-7e8f9a0b1c2d",
"inbox_id": "0199f5c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
"from": "Bruno <[email protected]>",
"to": ["[email protected]"],
"subject": "Factura del pedido #10475",
"size": 66103,
"otp_token": null,
"text": "¿Puedes reenviar la factura? Adjunto el pedido.",
"html": "<p>¿Puedes reenviar la factura? Adjunto el pedido.</p>",
"headers": { "date": ["Wed, 4 Nov 2026 09:41:07 -0300"] },
"attachments": [
{
"filename": "nota-fiscal-10475.pdf",
"content_type": "application/pdf",
"size": 48213,
"download_url": "https://api.postal.so/received-attachments/…"
}
]
}- thread_id agrupa las respuestas de la misma conversación, por los encabezados In-Reply-To y References
- otp_token trae el código y el origen cuando el correo tiene un encabezado OTP-Token válido; no va en el webhook
- El original, tal como llegó al servidor, sale de GET /emails/receiving/{id}/raw
Casos de uso
Lo que puedes construir
Casos que funcionan hoy con el webhook, la API de recibidos y la API de envío.
Agente de IA con correo
El agente lee cada mensaje por el webhook y la API y responde con POST /emails, pasando In-Reply-To y References para que la respuesta quede en la misma conversación.
Soporte y tickets
Cada correo a suporte@ abre o actualiza un ticket en tu sistema. El thread_id agrupa las respuestas de la misma conversación.
Responder por correo
Con un catch-all, cada pedido o comentario tiene su propia dirección, como [email protected]. La respuesta llega con esa dirección en to.
Registro en el CRM
Copia o reenvía las conversaciones con clientes a una dirección de Postal y registra cada mensaje en el historial del contacto, por remitente.
Lectura de adjuntos
Facturas, currículos y hojas de cálculo llegan con nombre, tipo y tamaño. Tu código descarga el archivo por un enlace firmado y lo procesa.
Pruebas con códigos de acceso
En las pruebas automatizadas, el correo de inicio de sesión llega a un buzón de prueba y la API devuelve el código del encabezado OTP-Token, listo para escribir.
Seguridad
Seguro de punta a punta
Lo que hace Postal para que a tu sistema solo llegue lo que vino de ella.
Aviso firmado
Cada POST lleva webhook-id, webhook-timestamp y webhook-signature (HMAC-SHA256, estándar Standard Webhooks). Los SDK lo comprueban y rechazan avisos con más de 5 minutos.
Solo entra lo que tiene destino
El servidor de entrada solo acepta mensajes para direcciones que tienen buzón; los demás se rechazan en el momento, sin rebotes después. Los mensajes de más de 25 MB, también.
Contenido detrás de tu clave
El webhook lleva solo el resumen. El texto, el HTML, los adjuntos y el original piden una clave de API de acceso total, y los enlaces de los adjuntos caducan en 1 hora.
Comparación
¿Inbound o buzones de entrada?
Es el mismo buzón, usado de dos maneras. Puedes usar las dos a la vez.
| Inbound | Buzones de entrada | |
|---|---|---|
| Quién trabaja | Tu código | Las personas del equipo, en el panel |
| Cómo llega | Webhook email.received y API | Conversaciones en el panel, con carpetas, etiquetas y responsable |
| Responder | Desde tu sistema, con POST /emails | En la conversación, desde el panel o por la API |
| Direcciones | Exacta, catch-all o en in.postal.so | Las mismas |
| Para qué sirve | Procesar lo que llega: tickets, adjuntos, respuestas, pruebas | Atender a clientes y organizar la bandeja del equipo |
¿Necesitas que alguien del equipo lea y responda? Mira los buzones de entrada.
Alcance
Alcance y límites
Lo que vale hoy, sin letra pequeña.
| Elemento | Cómo funciona |
|---|---|
| Planes | En el plan Gratis, hasta 3 buzones de entrada con dirección en in.postal.so. Las direcciones en tu dominio, catch-all incluido, son de los planes de pago. Consulta los planes y precios. |
| Tamaño | Hasta 25 MB por mensaje, con los adjuntos. Por encima, el servidor lo rechaza en la entrega. |
| Direcciones | Solo entran mensajes para direcciones que tienen buzón. Un correo a otra dirección del dominio se rechaza, salvo que exista un catch-all. |
| Ritmo | Cada servidor remitente entrega hasta 60 mensajes por minuto; por encima, recibe un aviso temporal y vuelve a intentarlo. |
| Webhook | Responde 2xx en hasta 15 segundos. Si no, Postal vuelve a intentarlo hasta 8 veces, en unas 27 horas. |
| Almacenamiento | Los mensajes, los adjuntos y el original se guardan durante el plazo de retención del plan, 30 días por defecto, y después se borran. Guarda en tu sistema lo que necesites conservar. |
| Envío | Recibir no usa la cuota de envíos. Las respuestas y los reenvíos salen como envíos normales, desde un dominio verificado. |
| Lo que no hace | Postal no filtra spam ni virus y no registra SPF, DKIM ni DMARC de los mensajes recibidos. El original, con todos los encabezados, queda disponible para que tu código lo compruebe. |
Preguntas frecuentes
¿Necesito un dominio propio?
No para empezar. Postal genera una dirección en in.postal.so que recibe al instante, también en el plan Gratis. Para recibir en direcciones de tu dominio, catch-all incluido, usa un plan de pago, activa la recepción en el dominio y apunta el MX a mx.postal.so.
¿Cómo sé cuándo llega un correo?
Registra un webhook con el evento Recibido (email.received): Postal envía un POST firmado en cuanto guarda el mensaje. Si prefieres consultar, lista los mensajes con GET /emails/receiving, filtrando por buzón o solo los no leídos.
¿Puedo responder por la API?
Sí. Envía la respuesta con POST /emails, desde un dominio verificado, con los encabezados In-Reply-To y References apuntando al message_id recibido, para que quede en la misma conversación. En un buzón de un dominio tuyo verificado, también puedes responder en la propia conversación, por la API de buzones.
¿Cómo accedo a los adjuntos?
El webhook lista los adjuntos con nombre, tipo y tamaño. GET /emails/receiving/{id} devuelve cada uno con download_url, un enlace firmado que vale 1 hora y no necesita la clave. El mensaje original sale de GET /emails/receiving/{id}/raw.
¿Qué comprobaciones de seguridad se hacen?
Cada aviso del webhook va firmado, y el servidor de entrada solo acepta mensajes de hasta 25 MB para direcciones que tienen buzón. Postal no filtra spam ni virus y no registra SPF, DKIM ni DMARC de los mensajes recibidos: si tu flujo depende de eso, compruébalo en tu código a partir del mensaje original.
¿Cuál es la diferencia entre Inbound y los buzones de entrada?
Es el mismo buzón, usado de dos maneras. Con Inbound, tu código procesa cada mensaje por el webhook y la API. Con los buzones de entrada, el equipo lee y responde en el panel, con conversaciones, etiquetas y responsable.
Empieza a recibir correos en tu app
Crea tu cuenta gratis y una dirección en in.postal.so en pocos minutos.