Automatizaciones
Secuencias de correos y acciones disparadas por eventos, contactos nuevos, segmentos, formularios, etiquetas, aperturas y clics, con pruebas A/B, versiones, programación y el registro de cada ejecución.
Las automatizaciones envían correos y se ocupan de los contactos solas, a partir de un disparador: un evento de tu producto, un contacto nuevo, la entrada a un segmento o la salida de él, una suscripción a un tema o por formulario. Cada contacto que activa el disparador recibe una ejecución, que recorre los pasos (esperar, enviar un correo, comprobar una condición, cambiar el contacto, llamar a un webhook) y registra lo que ocurrió en cada uno. Todos los endpoints exigen una clave de acceso total.
Los disparadores incluyen además una etiqueta que recibe el contacto, la apertura o el clic en un correo y el cambio de un campo o de una propiedad del contacto; entre los pasos hay esperar hasta que el contacto abra, haga clic o responda un correo (wait_for) y dividir los contactos entre variantes en una prueba A/B (experiment). Una automatización se puede activar y pausar en horarios programados.
#Borrador, activa y pausada
| Estado | Cómo funciona |
|---|---|
draft | Se crea así. Se puede guardar incompleta (un correo sin plantilla, una condición sin caminos); lo que falta aparece en issues. No inicia ejecuciones. |
active | Después de activarla: la definición debe estar completa y se convierte en una versión (version). Cada disparador inicia una ejecución. |
paused | Después de pausarla: no inicia ejecuciones, y las que están esperando quedan detenidas hasta que la automatización se active de nuevo. |
Versiones. Editar una automatización activa publica una versión nueva al instante (si cambiaron el disparador, los pasos o reentry). Solo las ejecuciones nuevas siguen la versión nueva: las que ya están en curso terminan en la versión en que empezaron (version de la ejecución). Cambiar solo el nombre no crea una versión.
Reentrada. Con reentry: "once" (predeterminado), cada contacto pasa por la automatización una sola vez. Con "every_time", cada disparador inicia una ejecución nueva, aunque haya otra en curso.
Límite del plan. Las ejecuciones iniciadas en el mes (en UTC), sumando todas las automatizaciones, tienen un límite por plan (consulta Límites y automation_runs en GET /usage). Si pasas el límite, la ejecución queda registrada como skipped, con el motivo, y no cuenta para el límite.
Programación. POST /automations/{id}/schedule fija la hora en que la automatización se activa (activate_at) y/o se pausa (deactivate_at), por ejemplo para una promoción con inicio y fin. Los horarios aparecen en la automatización y se aplican cada minuto; una vez aplicado, el campo vuelve a null. Una automatización programada para activarse debe estar completa, como una activa.
Límites del plan. Además de las ejecuciones por mes, el plan puede limitar cuántas automatizaciones tiene el equipo (automations, que solo rige al crear: crear por encima del límite responde 403 plan_limit_reached) y si incluye pruebas A/B (automation_experiments). Las cifras de cada plan están en GET /plans. Cuando el plan baja y pasa a permitir menos automatizaciones de las que tiene el equipo, las más nuevas por encima del límite quedan con paused_by_plan: true: siguen listadas y legibles, pero no inician ejecuciones y editar, activar, programar o declarar una ganadora responde 403 paused_by_plan hasta que el equipo vuelva al límite (mejorando el plan o borrando otras).
#Disparador y pasos
Una automatización es un trigger y una lista de steps. Los pasos se ejecutan en orden; una condition cierra la lista y continúa por uno de los caminos (branches.yes o branches.no), cada uno con su propia lista; un experiment continúa por el camino de la variante sorteada (branches.a, branches.b… o branches.control). El final de una lista termina la ejecución. Una automatización puede tener hasta 100 pasos en total. Un wait_for también cierra la lista y continúa por branches.yes o branches.no.
Disparadores
type | Campos | Cuándo se inicia | Ejemplo |
|---|---|---|---|
event | event: nombre del evento. filters: hasta 10 comparaciones con las propiedades del evento, todas deben cumplirse. | Llega un evento con ese nombre junto con un contacto. | {"type": "event", "event": "order.paid", "filters": [{"key": "total", "operator": "gte", "value": 500}]} |
contact.created | — | Se crea un contacto por la API, por un formulario o por un evento con un correo nuevo. Los contactos de una importación de CSV solo activan cuando la importación lo pide (trigger_automations, consulta Importar un CSV). | {"type": "contact.created"} |
segment.added | segment_id | Un contacto entra en el segmento: por la API, por un formulario o por un paso update_contact o, en un segmento dinámico, cuando empieza a cumplir los filtros (una vez por entrada, que también avisa el webhook contact.segment_entered). Guardar un segmento dinámico no dispara nada para quien ya cumple los filtros. Las importaciones solo disparan con trigger_automations. | {"type": "segment.added", "segment_id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a"} |
segment.removed | segment_id | Un contacto sale del segmento: lo quitan de un segmento estático (por la API, por el panel o por un paso update_contact) o deja de cumplir los filtros de un segmento dinámico cuando este se recalcula. Cada salida dispara una vez (y el webhook contact.segment_exited avisa). Las importaciones no disparan. | {"type": "segment.removed", "segment_id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a"} |
topic.subscribed | topic_id | La elección del contacto para el tema pasa a ser opt_in (antes no había elección o era opt_out): en la página de preferencias, en un formulario, por la API o por una automatización. Las importaciones y la propia automatización que hizo el cambio no disparan. | {"type": "topic.subscribed", "topic_id": "019a1d95-4c5d-7e6f-8a7b-8c9d0e1f2a3b"} |
topic.unsubscribed | topic_id | Lo contrario: la elección pasa a ser opt_out. | {"type": "topic.unsubscribed", "topic_id": "019a1d95-4c5d-7e6f-8a7b-8c9d0e1f2a3b"} |
form.submitted | form_id | Alguien se suscribe mediante el formulario (con doble opt-in, después de confirmar). | {"type": "form.submitted", "form_id": "019a1da0-7f80-7b91-8c0d-2e3f4a5b6c7d"} |
tag.added | tag_id (o tag_name, para una etiqueta que aún no existe: se crea al guardar) | El contacto recibe una etiqueta que aún no tenía: por la API, por el panel o por un paso update_contact. Las importaciones solo disparan con trigger_automations. Consulta Contactos. | {"type": "tag.added", "tag_name": "cliente-vip"} |
email.opened | Filtros opcionales: template_id, broadcast_id y automation_id; todos los que indiques deben coincidir con el correo electrónico. | Un contacto abre un correo electrónico. Un clic también cuenta como apertura, ya que las aperturas no se miden cuando las imágenes están bloqueadas. El contacto es el del correo de una campaña o automatización y, en los demás, el que tiene la dirección del primer destinatario. Cada automatización inicia como máximo una ejecución para el mismo contacto y correo. | {"type": "email.opened", "broadcast_id": "019a1f00-2c3d-7e4f-8a5b-6c7d8e9f0a1b"} |
email.clicked | Los mismos filtros de email.opened, más link_contains: un fragmento del enlace en el que se hizo clic (sin distinguir mayúsculas, hasta 500 caracteres). | Un contacto hace clic en un enlace de un correo electrónico. Un clic inicia las automatizaciones de email.clicked y las de email.opened. | {"type": "email.clicked", "link_contains": "/checkout"} |
contact.property_changed | property: first_name, last_name, email, unsubscribed, tags o la clave de una propiedad del contacto. Opcionales: from (valor anterior) y to (valor posterior); vacío vale para cualquier valor. | El campo o la propiedad cambia, por cualquier vía (API, panel, formulario, evento con contact_properties u otra automatización). Un contacto nuevo cuenta como un cambio de vacío a cada valor con el que nació. En tags, to es la etiqueta añadida y from, la quitada. Los textos se comparan sin distinguir mayúsculas y los números como números. La automatización que hizo el cambio, en un paso update_contact, no se inicia de nuevo; las importaciones nunca disparan. | {"type": "contact.property_changed", "property": "plan", "from": "free", "to": "pro"} |
Pasos
Cada paso es {"id", "type", "config"}. El id (letras, números, _ y -, hasta 64 caracteres, único en la automatización) identifica el paso en las condiciones y en el registro de las ejecuciones; si falta, Postal genera uno como step_k3x9p2qa7m. Las claves desconocidas se descartan.
type | Qué hace | config |
|---|---|---|
wait | Espera un tiempo o hasta una hora. | {"mode": "duration", "amount": 2, "unit": "days"} (unidades: minutes, hours, days, weeks; hasta 365 días) o {"mode": "until", "time": "09:00", "weekdays": [1, 2, 3, 4, 5], "timezone": "America/Sao_Paulo"}: las próximas 09:00, solo en los días de la semana indicados (1 = lunes, 7 = domingo; vacío = cualquier día). Zona horaria predeterminada: America/Sao_Paulo. |
wait_for | Espera a que el contacto abra, haga clic o responda a un correo electrónico enviado antes, hasta un plazo. | {"step_id": "welcome", "activity": "opened", "timeout": {"amount": 2, "unit": "days"}}. Sigue por branches.yes en cuanto ocurre o por branches.no cuando vence el plazo. Siempre va al final de su lista. Consulta Esperar una acción en el correo. |
send_email | Envía una plantilla publicada al contacto. | template_id (ID o alias, obligatorio), from (predeterminado: el de la plantilla), subject (en lugar del asunto de la plantilla), reply_to y variables. Consulta Variables. Con detect_replies: true, Postal detecta las respuestas al correo, algo que wait_for y las condiciones de respuesta necesitan (consulta Detectar respuestas). |
condition | Sigue por el camino yes o no. | Siempre va al final de su lista; los pasos siguientes van en branches.yes y branches.no. Consulta Condiciones. |
update_contact | Modifica el contacto. | actions, hasta 20: {"type": "set_property", "key": "plan", "value": "pro"} (first_name, last_name o una propiedad; el valor acepta {{event.*}}), add_to_segment y remove_from_segment (con segment_id), subscribe_topic y unsubscribe_topic (con topic_id), unsubscribe y resubscribe. También add_tag y remove_tag (con tag_id o, para una etiqueta que aún no existe, tag_name: se crea al guardar). Los segmentos dinámicos no aceptan add_to_segment ni remove_from_segment: solo los estáticos. |
webhook | Envía el contacto y el evento a una URL tuya. | {"url": "https://…"}. Consulta Paso de webhook. |
experiment | Divide los contactos entre variantes (prueba A/B). | De 2 a 4 variants, cada una un camino en branches, y un grupo de control opcional. Siempre va al final de su lista. Consulta Experimentos. |
exit | Termina la ejecución. | Sin configuración; va al final de su lista. El final de una lista ya termina la ejecución, así que usa exit solo para dejarlo claro. |
Condiciones
kind | Campos | Ejemplo |
|---|---|---|
contact_property | key (email, first_name, last_name o una propiedad), operator, value | {"kind": "contact_property", "key": "plan", "operator": "equals", "value": "pro"} |
event_property | key (ruta dentro de la propiedad del evento que inició la ejecución, como items.0.name), operator, value | {"kind": "event_property", "key": "total", "operator": "gt", "value": 200} |
segment | segment_id, operator: in o not_in | {"kind": "segment", "segment_id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a", "operator": "in"} |
topic | topic_id, operator: subscribed o not_subscribed | {"kind": "topic", "topic_id": "019a1d95-4c5d-7e6f-8a7b-8c9d0e1f2a3b", "operator": "subscribed"} |
tag | tag_id (o tag_name), operator: has o not_has | {"kind": "tag", "tag_id": "019a1d85-6c7d-7e8f-9a0b-1c2d3e4f5a6b", "operator": "has"} |
email_activity | step_id de un send_email anterior en el mismo camino, activity: opened o clicked. Un clic cuenta como apertura. También acepta clicked_link (con link_contains, un fragmento del enlace en el que se hizo clic) y replied (el correo debe tener detect_replies). Una respuesta que no sea automática también cuenta como apertura. | {"kind": "email_activity", "step_id": "welcome", "activity": "opened"} |
Operadores de comparación (en las condiciones de propiedad y en los filtros del disparador): equals, not_equals, contains, not_contains, starts_with, ends_with, gt, gte, lt, lte, is_set e is_not_set (estos dos sin value). Los textos se comparan sin distinguir mayúsculas; los números, como números; las fechas en formato AAAA-MM-DD, en orden de calendario.
Esperar una acción en el correo
El paso wait_for retiene la ejecución hasta que el contacto abra, haga clic o responda a un correo electrónico enviado por un send_email anterior, en el mismo camino. Si la acción ocurre, la ejecución sigue al instante por branches.yes; si el timeout vence antes, sigue por branches.no. Es mejor que una espera seguida de una condición de actividad: reacciona en cuanto la persona actúa, en lugar de mirar solo al final del plazo.
{
"id": "wait_open",
"type": "wait_for",
"config": {
"step_id": "welcome",
"activity": "opened",
"timeout": {
"amount": 2,
"unit": "days"
}
},
"branches": {
"yes": [
{
"id": "tag_engaged",
"type": "update_contact",
"config": {
"actions": [
{
"type": "add_to_segment",
"segment_id": "019a1d91-4c5d-7e6f-8a7b-8c9d0e1f2a3c"
}
]
}
}
],
"no": [
{
"id": "reminder",
"type": "send_email",
"config": {
"template_id": "trial-help",
"subject": "{{contact.first_name}}, posso ajudar com o seu teste?"
}
}
]
}
}
config | Qué es |
|---|---|
step_id | El id de un send_email anterior en el mismo camino. Obligatorio. |
activity | opened (predeterminado; un clic o una respuesta también cuentan), clicked, clicked_link o replied. |
link_contains | Solo con clicked_link: un fragmento del enlace en el que se hizo clic, sin distinguir mayúsculas, hasta 500 caracteres. |
timeout | {"amount": 2, "unit": "days"}: cuánto esperar como máximo (minutes, hours, days o weeks; hasta 365 días). Obligatorio. |
repliedexigedetect_replies: trueen elsend_email; sin eso la automatización no se puede activar. Las respuestas automáticas (fuera de la oficina) no cuentan.- Si el correo del
step_idno salió en esta ejecución (por ejemplo, el contacto se dio de baja y se omitió el envío), la ejecución sigue porbranches.no. - En el registro de la ejecución, el
outputindica cómo terminó:resolved_byesevent(conresolved_at),timeoutono_email.
Variables en los correos
El send_email usa la versión publicada de la plantilla, como un POST /emails con template: mismas validaciones, cuota de envío y lista de supresión. Las variables de la plantilla ({{{NAME}}}) se rellenan solas con:
EMAIL,FIRST_NAME,LAST_NAMEyCONTACT_IDdel contacto;- cada propiedad del contacto y cada propiedad de primer nivel del evento, por su clave y en mayúsculas (
planyPLAN); POSTAL_UNSUBSCRIBE_URL, el enlace a la página de preferencias del contacto;- y las
variablesdel paso, que aceptan referencias como{{contact.first_name}},{{contact.plan}}y{{event.items.0.name}}(los valores ausentes quedan vacíos).
El subject del paso y los valores de set_property aceptan las mismas referencias {{contact.*}} y {{event.*}}. Los correos llevan las cabeceras List-Unsubscribe con un solo clic; incluye {{{POSTAL_UNSUBSCRIBE_URL}}} en la plantilla para mostrar el enlace en el cuerpo. Los contactos dados de baja (unsubscribed) no reciben nada: el paso queda skipped y la ejecución continúa. Si el envío falla (cuota agotada, dominio sin verificar, variable sin valor), el paso y la ejecución quedan failed.
#Experimentos (prueba A/B)
El paso experiment divide los contactos que llegan a él entre 2 y 4 variantes, según el porcentaje de cada una, para comparar asuntos, contenidos o esperas. Cada variante es un camino en branches, con la clave de la variante (letras minúsculas, números y _, como a y b). Con control_percent por encima de 0, esa parte de los contactos va al camino control, que puede quedar vacío: esos contactos se saltan las variantes y terminan ahí. Los porcentajes de las variantes más el del control suman 100. Las claves empiezan con una letra, tienen hasta 32 caracteres y no pueden ser control; una variante sin clave recibe la siguiente letra libre (a, b…).
{
"id": "subject_test",
"type": "experiment",
"config": {
"variants": [
{
"key": "a",
"name": "Assunto curto",
"percent": 45
},
{
"key": "b",
"name": "Assunto com o nome",
"percent": 45
}
],
"control_percent": 10,
"goal_event": "order.paid"
},
"branches": {
"a": [
{
"id": "email_short",
"type": "send_email",
"config": {
"template_id": "trial-welcome",
"subject": "Seu teste começou"
}
}
],
"b": [
{
"id": "email_named",
"type": "send_email",
"config": {
"template_id": "trial-welcome",
"subject": "{{contact.first_name}}, seu teste começou"
}
}
],
"control": []
}
}
- Siempre la misma variante. La variante sale de un sorteo determinista por la ejecución y el paso, y queda guardada: la ejecución siempre sigue el mismo camino, y cada ejecución aparece con
{"variant", "reason"}en el registro del paso. - Conversión. Con
goal_event, un evento con ese nombre enviado por el contacto después de entrar en la variante cuenta como conversión. - Resultados.
GET /automations/{id}/experimentsdevuelve, por variante, cuántos contactos entraron, los correos enviados después de la división y cuántos de esos contactos abrieron, hicieron clic, respondieron (las respuestas automáticas no cuentan) y convirtieron. - Ganadora.
POST /automations/{id}/experiments/{step_id}/winnerenvía a todos los próximos contactos a la variante elegida, en cualquier versión de la automatización, y registra quién la declaró y cuándo. Quien ya pasó sigue en la variante que recibió. - Cambiar la división. Las variantes y los porcentajes de un experimento solo se pueden cambiar con la automatización pausada (en una activa, la respuesta es
422). Los nombres y los pasos dentro de las variantes se pueden cambiar en cualquier momento. - Plan. Los experimentos dependen del recurso
automation_experimentsdel plan. Sin él, crear, guardar, activar o programar una automatización con un experimento responde403 plan_limit_reached. Si el equipo pierde el recurso con la automatización activa, esta sigue funcionando y todos los contactos van a la primera variante. El detalle de la automatización incluyeexperiments_included.
#Listar automatizaciones
GET/automations
Cada automatización con su definición, la versión vigente (null en un borrador nunca activado), runs_this_month (ejecuciones iniciadas en el mes) y runs_in_progress.
Parámetros de consulta
limitintegerDe 1 a 100. Predeterminado: 20.afterstringCursor de la página siguiente.beforestringCursor de la página anterior.searchstringBúsqueda por nombre.statusstringdraft,activeopaused.scheduledbooleanContrue, solo las automatizaciones con una activación o una pausa programada.
cURL
curl 'https://api.postal.so/automations?status=active' \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/automations?status=active', {
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/automations",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
params={
"status": "active",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->get('automations', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'query' => [
'status' => 'active',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "list",
"has_more": false,
"data": [
{
"object": "automation",
"id": "019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f",
"paused_by_plan": false,
"name": "Boas-vindas do teste grátis",
"status": "active",
"trigger": {
"type": "event",
"event": "trial.started",
"filters": [
{
"key": "plan",
"operator": "equals",
"value": "pro"
}
]
},
"reentry": "once",
"steps": [
{
"id": "welcome",
"type": "send_email",
"config": {
"template_id": "trial-welcome",
"from": "Ana da Loja Exemplo <[email protected]>",
"subject": null,
"reply_to": null,
"variables": {
"DAYS": "{{event.trial_days}}"
}
}
},
{
"id": "wait_2_days",
"type": "wait",
"config": {
"mode": "duration",
"amount": 2,
"unit": "days"
}
},
{
"id": "check_opened",
"type": "condition",
"config": {
"kind": "email_activity",
"step_id": "welcome",
"activity": "opened"
},
"branches": {
"yes": [
{
"id": "tag_engaged",
"type": "update_contact",
"config": {
"actions": [
{
"type": "add_to_segment",
"segment_id": "019a1d91-4c5d-7e6f-8a7b-8c9d0e1f2a3c"
}
]
}
}
],
"no": [
{
"id": "wait_morning",
"type": "wait",
"config": {
"mode": "until",
"time": "09:00",
"weekdays": [1, 2, 3, 4, 5],
"timezone": "America/Sao_Paulo"
}
},
{
"id": "reminder",
"type": "send_email",
"config": {
"template_id": "trial-help",
"from": null,
"subject": "{{contact.first_name}}, posso ajudar com o seu teste?",
"reply_to": null,
"variables": {}
}
},
{
"id": "notify_crm",
"type": "webhook",
"config": {
"url": "https://crm.lojaexemplo.com.br/hooks/postal"
}
}
]
}
}
],
"version": 2,
"runs_this_month": 318,
"runs_in_progress": 41,
"activated_at": "2026-10-15T13:00:00.000000Z",
"activate_at": null,
"deactivate_at": null,
"created_at": "2026-10-14T19:22:00.000000Z",
"updated_at": "2026-10-20T10:31:00.000000Z"
}
]
}
#Crear una automatización
POST/automations
Crea un borrador. Los errores de estructura rechazan el guardado (un tipo de paso desconocido, un paso después de una condición, un segmento de otro equipo, un ID repetido); lo que simplemente falta entra en issues y solo impide la activación. Devuelve la automatización completa, con issues y el webhook_signing_secret usado en los pasos de webhook.
Cuerpo de la solicitud
namestringobligatorioNombre, hasta 255 caracteres.triggerobjectobligatorioEl disparador, contype.stepsobject[]Los pasos. Puede quedar vacío en un borrador.reentrystringonce(predeterminado) oevery_time.
Errores
- 422
validation_errorCampo no válido o definición con errores. El campo extraerrorsenumera cada problema, con elstep_id(onull, para el disparador) y el mensaje. - 403
plan_limit_reachedLa definición tiene un experimento y el plan no incluye pruebas A/B. - 403
plan_limit_reachedEl plan no permite más automatizaciones (límiteautomations).
cURL
curl -X POST https://api.postal.so/automations \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Boas-vindas do teste grátis",
"trigger": {
"type": "event",
"event": "trial.started",
"filters": [
{
"key": "plan",
"operator": "equals",
"value": "pro"
}
]
},
"steps": [
{
"id": "welcome",
"type": "send_email",
"config": {
"template_id": "trial-welcome",
"from": "Ana da Loja Exemplo <[email protected]>",
"variables": {
"DAYS": "{{event.trial_days}}"
}
}
},
{
"id": "wait_2_days",
"type": "wait",
"config": {
"mode": "duration",
"amount": 2,
"unit": "days"
}
},
{
"id": "check_opened",
"type": "condition",
"config": {
"kind": "email_activity",
"step_id": "welcome",
"activity": "opened"
},
"branches": {
"yes": [
{
"id": "tag_engaged",
"type": "update_contact",
"config": {
"actions": [
{
"type": "add_to_segment",
"segment_id": "019a1d91-4c5d-7e6f-8a7b-8c9d0e1f2a3c"
}
]
}
}
],
"no": [
{
"id": "wait_morning",
"type": "wait",
"config": {
"mode": "until",
"time": "09:00",
"weekdays": [1, 2, 3, 4, 5],
"timezone": "America/Sao_Paulo"
}
},
{
"id": "reminder",
"type": "send_email",
"config": {
"template_id": "trial-help",
"subject": "{{contact.first_name}}, posso ajudar com o seu teste?"
}
},
{
"id": "notify_crm",
"type": "webhook",
"config": {
"url": "https://crm.lojaexemplo.com.br/hooks/postal"
}
}
]
}
}
]
}'
Node.js
const response = await fetch('https://api.postal.so/automations', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Boas-vindas do teste grátis',
trigger: {
type: 'event',
event: 'trial.started',
filters: [
{
key: 'plan',
operator: 'equals',
value: 'pro',
},
],
},
steps: [
{
id: 'welcome',
type: 'send_email',
config: {
template_id: 'trial-welcome',
from: 'Ana da Loja Exemplo <[email protected]>',
variables: {
DAYS: '{{event.trial_days}}',
},
},
},
{
id: 'wait_2_days',
type: 'wait',
config: {
mode: 'duration',
amount: 2,
unit: 'days',
},
},
{
id: 'check_opened',
type: 'condition',
config: {
kind: 'email_activity',
step_id: 'welcome',
activity: 'opened',
},
branches: {
yes: [
{
id: 'tag_engaged',
type: 'update_contact',
config: {
actions: [
{
type: 'add_to_segment',
segment_id: '019a1d91-4c5d-7e6f-8a7b-8c9d0e1f2a3c',
},
],
},
},
],
no: [
{
id: 'wait_morning',
type: 'wait',
config: {
mode: 'until',
time: '09:00',
weekdays: [1, 2, 3, 4, 5],
timezone: 'America/Sao_Paulo',
},
},
{
id: 'reminder',
type: 'send_email',
config: {
template_id: 'trial-help',
subject: '{{contact.first_name}}, posso ajudar com o seu teste?',
},
},
{
id: 'notify_crm',
type: 'webhook',
config: {
url: 'https://crm.lojaexemplo.com.br/hooks/postal',
},
},
],
},
},
],
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/automations",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"name": "Boas-vindas do teste grátis",
"trigger": {
"type": "event",
"event": "trial.started",
"filters": [
{
"key": "plan",
"operator": "equals",
"value": "pro",
},
],
},
"steps": [
{
"id": "welcome",
"type": "send_email",
"config": {
"template_id": "trial-welcome",
"from": "Ana da Loja Exemplo <[email protected]>",
"variables": {
"DAYS": "{{event.trial_days}}",
},
},
},
{
"id": "wait_2_days",
"type": "wait",
"config": {
"mode": "duration",
"amount": 2,
"unit": "days",
},
},
{
"id": "check_opened",
"type": "condition",
"config": {
"kind": "email_activity",
"step_id": "welcome",
"activity": "opened",
},
"branches": {
"yes": [
{
"id": "tag_engaged",
"type": "update_contact",
"config": {
"actions": [
{
"type": "add_to_segment",
"segment_id": "019a1d91-4c5d-7e6f-8a7b-8c9d0e1f2a3c",
},
],
},
},
],
"no": [
{
"id": "wait_morning",
"type": "wait",
"config": {
"mode": "until",
"time": "09:00",
"weekdays": [1, 2, 3, 4, 5],
"timezone": "America/Sao_Paulo",
},
},
{
"id": "reminder",
"type": "send_email",
"config": {
"template_id": "trial-help",
"subject": "{{contact.first_name}}, posso ajudar com o seu teste?",
},
},
{
"id": "notify_crm",
"type": "webhook",
"config": {
"url": "https://crm.lojaexemplo.com.br/hooks/postal",
},
},
],
},
},
],
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('automations', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'name' => 'Boas-vindas do teste grátis',
'trigger' => [
'type' => 'event',
'event' => 'trial.started',
'filters' => [
[
'key' => 'plan',
'operator' => 'equals',
'value' => 'pro',
],
],
],
'steps' => [
[
'id' => 'welcome',
'type' => 'send_email',
'config' => [
'template_id' => 'trial-welcome',
'from' => 'Ana da Loja Exemplo <[email protected]>',
'variables' => [
'DAYS' => '{{event.trial_days}}',
],
],
],
[
'id' => 'wait_2_days',
'type' => 'wait',
'config' => [
'mode' => 'duration',
'amount' => 2,
'unit' => 'days',
],
],
[
'id' => 'check_opened',
'type' => 'condition',
'config' => [
'kind' => 'email_activity',
'step_id' => 'welcome',
'activity' => 'opened',
],
'branches' => [
'yes' => [
[
'id' => 'tag_engaged',
'type' => 'update_contact',
'config' => [
'actions' => [
[
'type' => 'add_to_segment',
'segment_id' => '019a1d91-4c5d-7e6f-8a7b-8c9d0e1f2a3c',
],
],
],
],
],
'no' => [
[
'id' => 'wait_morning',
'type' => 'wait',
'config' => [
'mode' => 'until',
'time' => '09:00',
'weekdays' => [1, 2, 3, 4, 5],
'timezone' => 'America/Sao_Paulo',
],
],
[
'id' => 'reminder',
'type' => 'send_email',
'config' => [
'template_id' => 'trial-help',
'subject' => '{{contact.first_name}}, posso ajudar com o seu teste?',
],
],
[
'id' => 'notify_crm',
'type' => 'webhook',
'config' => [
'url' => 'https://crm.lojaexemplo.com.br/hooks/postal',
],
],
],
],
],
],
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "automation",
"id": "019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f",
"paused_by_plan": false,
"name": "Boas-vindas do teste grátis",
"status": "draft",
"trigger": {
"type": "event",
"event": "trial.started",
"filters": [
{
"key": "plan",
"operator": "equals",
"value": "pro"
}
]
},
"reentry": "once",
"steps": [
{
"id": "welcome",
"type": "send_email",
"config": {
"template_id": "trial-welcome",
"from": "Ana da Loja Exemplo <[email protected]>",
"subject": null,
"reply_to": null,
"variables": {
"DAYS": "{{event.trial_days}}"
}
}
},
{
"id": "wait_2_days",
"type": "wait",
"config": {
"mode": "duration",
"amount": 2,
"unit": "days"
}
},
{
"id": "check_opened",
"type": "condition",
"config": {
"kind": "email_activity",
"step_id": "welcome",
"activity": "opened"
},
"branches": {
"yes": [
{
"id": "tag_engaged",
"type": "update_contact",
"config": {
"actions": [
{
"type": "add_to_segment",
"segment_id": "019a1d91-4c5d-7e6f-8a7b-8c9d0e1f2a3c"
}
]
}
}
],
"no": [
{
"id": "wait_morning",
"type": "wait",
"config": {
"mode": "until",
"time": "09:00",
"weekdays": [1, 2, 3, 4, 5],
"timezone": "America/Sao_Paulo"
}
},
{
"id": "reminder",
"type": "send_email",
"config": {
"template_id": "trial-help",
"from": null,
"subject": "{{contact.first_name}}, posso ajudar com o seu teste?",
"reply_to": null,
"variables": {}
}
},
{
"id": "notify_crm",
"type": "webhook",
"config": {
"url": "https://crm.lojaexemplo.com.br/hooks/postal"
}
}
]
}
}
],
"version": null,
"runs_this_month": 0,
"runs_in_progress": 0,
"activated_at": null,
"activate_at": null,
"deactivate_at": null,
"created_at": "2026-10-14T19:22:00.000000Z",
"updated_at": "2026-10-14T19:22:00.000000Z",
"issues": [],
"webhook_signing_secret": "whsec_YXV0b21hY2FvLWV4ZW1wbG8tYWJjZGVmMTIzNA==",
"experiments_included": true
}
{
"statusCode": 422,
"name": "validation_error",
"message": "A condition must be the last step of its path: continue in its branches.",
"errors": [
{
"step_id": "check_opened",
"message": "A condition must be the last step of its path: continue in its branches."
}
]
}
#Obtener una automatización
GET/automations/{id}
Devuelve la automatización con issues (lo que impide la activación, cada uno con step_id y message) y el webhook_signing_secret.
Parámetros de ruta
idstringobligatorioID de la automatización.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo.
cURL
curl https://api.postal.so/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f', {
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/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f",
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('automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "automation",
"id": "019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f",
"paused_by_plan": false,
"name": "Boas-vindas do teste grátis",
"status": "active",
"trigger": {
"type": "event",
"event": "trial.started",
"filters": [
{
"key": "plan",
"operator": "equals",
"value": "pro"
}
]
},
"reentry": "once",
"steps": [
{
"id": "welcome",
"type": "send_email",
"config": {
"template_id": "trial-welcome",
"from": "Ana da Loja Exemplo <[email protected]>",
"subject": null,
"reply_to": null,
"variables": {
"DAYS": "{{event.trial_days}}"
}
}
},
{
"id": "wait_2_days",
"type": "wait",
"config": {
"mode": "duration",
"amount": 2,
"unit": "days"
}
},
{
"id": "check_opened",
"type": "condition",
"config": {
"kind": "email_activity",
"step_id": "welcome",
"activity": "opened"
},
"branches": {
"yes": [
{
"id": "tag_engaged",
"type": "update_contact",
"config": {
"actions": [
{
"type": "add_to_segment",
"segment_id": "019a1d91-4c5d-7e6f-8a7b-8c9d0e1f2a3c"
}
]
}
}
],
"no": [
{
"id": "wait_morning",
"type": "wait",
"config": {
"mode": "until",
"time": "09:00",
"weekdays": [1, 2, 3, 4, 5],
"timezone": "America/Sao_Paulo"
}
},
{
"id": "reminder",
"type": "send_email",
"config": {
"template_id": "trial-help",
"from": null,
"subject": "{{contact.first_name}}, posso ajudar com o seu teste?",
"reply_to": null,
"variables": {}
}
},
{
"id": "notify_crm",
"type": "webhook",
"config": {
"url": "https://crm.lojaexemplo.com.br/hooks/postal"
}
}
]
}
}
],
"version": 2,
"runs_this_month": 318,
"runs_in_progress": 41,
"activated_at": "2026-10-15T13:00:00.000000Z",
"activate_at": null,
"deactivate_at": null,
"created_at": "2026-10-14T19:22:00.000000Z",
"updated_at": "2026-10-20T10:31:00.000000Z",
"issues": [],
"webhook_signing_secret": "whsec_YXV0b21hY2FvLWV4ZW1wbG8tYWJjZGVmMTIzNA==",
"experiments_included": true
}
#Actualizar una automatización
PATCH/automations/{id}
Solo cambian los campos enviados; trigger y steps reemplazan por completo a los anteriores. En un borrador o en una automatización pausada, la definición puede quedar incompleta. En una automatización activa (o con una activación programada), debe estar completa, y el cambio en una activa se convierte en una nueva versión, que vale solo para las nuevas ejecuciones. Devuelve la automatización completa.
Parámetros de ruta
idstringobligatorioID de la automatización.
Cuerpo de la solicitud (todos opcionales)
namestringNombre nuevo.triggerobjectDisparador nuevo.stepsobject[]Nueva lista de pasos.reentrystringonceoevery_time.
Errores
- 422
validation_errorDefinición con errores o, en una automatización activa, incompleta o con la división de un experimento modificada. Consultaerrors. - 403
plan_limit_reachedLa definición tiene un experimento y el plan no incluye pruebas A/B. - 403
paused_by_planLa automatización está pausada porque superó el límite de automatizaciones del plan. Mejora tu plan o elimina otras. - 404
not_foundEl recurso no existe o es de otro equipo.
cURL
curl -X PATCH https://api.postal.so/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"reentry": "every_time"
}'
Node.js
const response = await fetch('https://api.postal.so/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
reentry: 'every_time',
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.patch(
"https://api.postal.so/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"reentry": "every_time",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->patch('automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'reentry' => 'every_time',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "automation",
"id": "019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f",
"paused_by_plan": false,
"name": "Boas-vindas do teste grátis",
"status": "active",
"trigger": {
"type": "event",
"event": "trial.started",
"filters": [
{
"key": "plan",
"operator": "equals",
"value": "pro"
}
]
},
"reentry": "every_time",
"steps": [
{
"id": "welcome",
"type": "send_email",
"config": {
"template_id": "trial-welcome",
"from": "Ana da Loja Exemplo <[email protected]>",
"subject": null,
"reply_to": null,
"variables": {
"DAYS": "{{event.trial_days}}"
}
}
},
{
"id": "wait_2_days",
"type": "wait",
"config": {
"mode": "duration",
"amount": 2,
"unit": "days"
}
},
{
"id": "check_opened",
"type": "condition",
"config": {
"kind": "email_activity",
"step_id": "welcome",
"activity": "opened"
},
"branches": {
"yes": [
{
"id": "tag_engaged",
"type": "update_contact",
"config": {
"actions": [
{
"type": "add_to_segment",
"segment_id": "019a1d91-4c5d-7e6f-8a7b-8c9d0e1f2a3c"
}
]
}
}
],
"no": [
{
"id": "wait_morning",
"type": "wait",
"config": {
"mode": "until",
"time": "09:00",
"weekdays": [1, 2, 3, 4, 5],
"timezone": "America/Sao_Paulo"
}
},
{
"id": "reminder",
"type": "send_email",
"config": {
"template_id": "trial-help",
"from": null,
"subject": "{{contact.first_name}}, posso ajudar com o seu teste?",
"reply_to": null,
"variables": {}
}
},
{
"id": "notify_crm",
"type": "webhook",
"config": {
"url": "https://crm.lojaexemplo.com.br/hooks/postal"
}
}
]
}
}
],
"version": 3,
"runs_this_month": 318,
"runs_in_progress": 41,
"activated_at": "2026-10-15T13:00:00.000000Z",
"activate_at": null,
"deactivate_at": null,
"created_at": "2026-10-14T19:22:00.000000Z",
"updated_at": "2026-10-24T16:30:00.000000Z",
"issues": [],
"webhook_signing_secret": "whsec_YXV0b21hY2FvLWV4ZW1wbG8tYWJjZGVmMTIzNA==",
"experiments_included": true
}
#Eliminar una automatización
DELETE/automations/{id}
Elimina la automatización con sus versiones y ejecuciones; las que estaban en curso se detienen. Para solo interrumpirla, es mejor pausarla.
Parámetros de ruta
idstringobligatorioID de la automatización.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo.
cURL
curl -X DELETE https://api.postal.so/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f', {
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/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f",
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('automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "automation",
"id": "019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f",
"deleted": true
}
#Activar
POST/automations/{id}/activate
Activa un borrador o reanuda una automatización pausada. La definición debe estar completa: plantillas elegidas y publicadas, remitente de un dominio verificado, caminos de las condiciones con al menos un paso, URLs de los webhooks… Publica la definición como versión (si cambió desde la última) y devuelve la automatización.
Parámetros de ruta
idstringobligatorioID de la automatización.
Errores
- 422
validation_errorLa definición tiene errores o aún está incompleta.errorsenumera todo lo que falta. - 403
plan_limit_reachedLa automatización tiene un experimento y el plan no incluye pruebas A/B. - 403
paused_by_planLa automatización está pausada porque superó el límite de automatizaciones del plan. Mejora tu plan o elimina otras. - 404
not_foundEl recurso no existe o es de otro equipo.
cURL
curl -X POST https://api.postal.so/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/activate \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/activate', {
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/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/activate",
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('automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/activate', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "automation",
"id": "019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f",
"paused_by_plan": false,
"name": "Boas-vindas do teste grátis",
"status": "active",
"trigger": {
"type": "event",
"event": "trial.started",
"filters": [
{
"key": "plan",
"operator": "equals",
"value": "pro"
}
]
},
"reentry": "once",
"steps": [
{
"id": "welcome",
"type": "send_email",
"config": {
"template_id": "trial-welcome",
"from": "Ana da Loja Exemplo <[email protected]>",
"subject": null,
"reply_to": null,
"variables": {
"DAYS": "{{event.trial_days}}"
}
}
},
{
"id": "wait_2_days",
"type": "wait",
"config": {
"mode": "duration",
"amount": 2,
"unit": "days"
}
},
{
"id": "check_opened",
"type": "condition",
"config": {
"kind": "email_activity",
"step_id": "welcome",
"activity": "opened"
},
"branches": {
"yes": [
{
"id": "tag_engaged",
"type": "update_contact",
"config": {
"actions": [
{
"type": "add_to_segment",
"segment_id": "019a1d91-4c5d-7e6f-8a7b-8c9d0e1f2a3c"
}
]
}
}
],
"no": [
{
"id": "wait_morning",
"type": "wait",
"config": {
"mode": "until",
"time": "09:00",
"weekdays": [1, 2, 3, 4, 5],
"timezone": "America/Sao_Paulo"
}
},
{
"id": "reminder",
"type": "send_email",
"config": {
"template_id": "trial-help",
"from": null,
"subject": "{{contact.first_name}}, posso ajudar com o seu teste?",
"reply_to": null,
"variables": {}
}
},
{
"id": "notify_crm",
"type": "webhook",
"config": {
"url": "https://crm.lojaexemplo.com.br/hooks/postal"
}
}
]
}
}
],
"version": 2,
"runs_this_month": 318,
"runs_in_progress": 41,
"activated_at": "2026-10-15T13:00:00.000000Z",
"activate_at": null,
"deactivate_at": null,
"created_at": "2026-10-14T19:22:00.000000Z",
"updated_at": "2026-10-20T10:31:00.000000Z",
"issues": [],
"webhook_signing_secret": "whsec_YXV0b21hY2FvLWV4ZW1wbG8tYWJjZGVmMTIzNA==",
"experiments_included": true
}
{
"statusCode": 422,
"name": "validation_error",
"message": "The automation is not complete yet. Choose the template to send.",
"errors": [
{
"step_id": "welcome",
"message": "Choose the template to send."
},
{
"step_id": null,
"message": "Choose the event that starts the automation."
}
]
}
#Pausar
POST/automations/{id}/pause
Deja de iniciar ejecuciones. Las que están esperando quedan detenidas y continúan desde donde estaban cuando la automatización se active de nuevo; una espera que venció entretanto sigue justo después.
Parámetros de ruta
idstringobligatorioID de la automatización.
Errores
- 422
validation_errorLa automatización no está activa. - 404
not_foundEl recurso no existe o es de otro equipo.
cURL
curl -X POST https://api.postal.so/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/pause \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/pause', {
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/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/pause",
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('automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/pause', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "automation",
"id": "019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f",
"paused_by_plan": false,
"name": "Boas-vindas do teste grátis",
"status": "paused",
"trigger": {
"type": "event",
"event": "trial.started",
"filters": [
{
"key": "plan",
"operator": "equals",
"value": "pro"
}
]
},
"reentry": "once",
"steps": [
{
"id": "welcome",
"type": "send_email",
"config": {
"template_id": "trial-welcome",
"from": "Ana da Loja Exemplo <[email protected]>",
"subject": null,
"reply_to": null,
"variables": {
"DAYS": "{{event.trial_days}}"
}
}
},
{
"id": "wait_2_days",
"type": "wait",
"config": {
"mode": "duration",
"amount": 2,
"unit": "days"
}
},
{
"id": "check_opened",
"type": "condition",
"config": {
"kind": "email_activity",
"step_id": "welcome",
"activity": "opened"
},
"branches": {
"yes": [
{
"id": "tag_engaged",
"type": "update_contact",
"config": {
"actions": [
{
"type": "add_to_segment",
"segment_id": "019a1d91-4c5d-7e6f-8a7b-8c9d0e1f2a3c"
}
]
}
}
],
"no": [
{
"id": "wait_morning",
"type": "wait",
"config": {
"mode": "until",
"time": "09:00",
"weekdays": [1, 2, 3, 4, 5],
"timezone": "America/Sao_Paulo"
}
},
{
"id": "reminder",
"type": "send_email",
"config": {
"template_id": "trial-help",
"from": null,
"subject": "{{contact.first_name}}, posso ajudar com o seu teste?",
"reply_to": null,
"variables": {}
}
},
{
"id": "notify_crm",
"type": "webhook",
"config": {
"url": "https://crm.lojaexemplo.com.br/hooks/postal"
}
}
]
}
}
],
"version": 2,
"runs_this_month": 318,
"runs_in_progress": 41,
"activated_at": "2026-10-15T13:00:00.000000Z",
"activate_at": null,
"deactivate_at": null,
"created_at": "2026-10-14T19:22:00.000000Z",
"updated_at": "2026-10-24T16:35:00.000000Z",
"issues": [],
"webhook_signing_secret": "whsec_YXV0b21hY2FvLWV4ZW1wbG8tYWJjZGVmMTIzNA==",
"experiments_included": true
}
#Programar la activación o la pausa
POST/automations/{id}/schedule
Programa cuándo se activa (activate_at) y/o se pausa (deactivate_at) la automatización, útil para una campaña con inicio y fin. Envía al menos uno de los dos; un campo que no envías mantiene el valor actual, y null lo borra. Las horas se aplican cada minuto, y cada una vuelve a null después de aplicarse. Devuelve la automatización completa.
Una activación solo se puede programar para una automatización que aún no está activa y que está completa (la misma exigencia que para activar; si deja de estar completa antes de la hora, la activación se descarta y la automatización queda como está). Una pausa necesita una automatización activa o una activación programada antes de ella.
Parámetros de ruta
idstringobligatorioID de la automatización.
Cuerpo de la solicitud
activate_atstring | nullCuándo activar, en ISO 8601 con zona horaria, como2026-11-02T09:00:00-03:00. En el futuro y hasta un año adelante.deactivate_atstring | nullCuándo pausar, en el mismo formato. Debe ser posterior aactivate_at, si se envían los dos.
Errores
- 422
validation_errorNinguno de los dos campos, hora que no es una fecha futura o supera un año,deactivate_atantes deactivate_at, activación de una automatización ya activa, pausa sin activación en una automatización que no está activa, o definición incompleta (errorsenumera lo que falta). - 403
plan_limit_reachedLa automatización tiene un experimento y el plan no incluye pruebas A/B. - 403
paused_by_planLa automatización está pausada porque superó el límite de automatizaciones del plan. Mejora tu plan o elimina otras. - 404
not_foundEl recurso no existe o es de otro equipo.
cURL
curl -X POST https://api.postal.so/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/schedule \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"activate_at": "2026-11-02T09:00:00-03:00",
"deactivate_at": "2026-11-09T23:59:00-03:00"
}'
Node.js
const response = await fetch('https://api.postal.so/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/schedule', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
activate_at: '2026-11-02T09:00:00-03:00',
deactivate_at: '2026-11-09T23:59:00-03:00',
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/schedule",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"activate_at": "2026-11-02T09:00:00-03:00",
"deactivate_at": "2026-11-09T23:59:00-03:00",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/schedule', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'activate_at' => '2026-11-02T09:00:00-03:00',
'deactivate_at' => '2026-11-09T23:59:00-03:00',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "automation",
"id": "019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f",
"paused_by_plan": false,
"name": "Boas-vindas do teste grátis",
"status": "draft",
"trigger": {
"type": "event",
"event": "trial.started",
"filters": [
{
"key": "plan",
"operator": "equals",
"value": "pro"
}
]
},
"reentry": "once",
"steps": [
{
"id": "welcome",
"type": "send_email",
"config": {
"template_id": "trial-welcome",
"from": "Ana da Loja Exemplo <[email protected]>",
"subject": null,
"reply_to": null,
"variables": {
"DAYS": "{{event.trial_days}}"
}
}
},
{
"id": "wait_2_days",
"type": "wait",
"config": {
"mode": "duration",
"amount": 2,
"unit": "days"
}
},
{
"id": "check_opened",
"type": "condition",
"config": {
"kind": "email_activity",
"step_id": "welcome",
"activity": "opened"
},
"branches": {
"yes": [
{
"id": "tag_engaged",
"type": "update_contact",
"config": {
"actions": [
{
"type": "add_to_segment",
"segment_id": "019a1d91-4c5d-7e6f-8a7b-8c9d0e1f2a3c"
}
]
}
}
],
"no": [
{
"id": "wait_morning",
"type": "wait",
"config": {
"mode": "until",
"time": "09:00",
"weekdays": [1, 2, 3, 4, 5],
"timezone": "America/Sao_Paulo"
}
},
{
"id": "reminder",
"type": "send_email",
"config": {
"template_id": "trial-help",
"from": null,
"subject": "{{contact.first_name}}, posso ajudar com o seu teste?",
"reply_to": null,
"variables": {}
}
},
{
"id": "notify_crm",
"type": "webhook",
"config": {
"url": "https://crm.lojaexemplo.com.br/hooks/postal"
}
}
]
}
}
],
"version": null,
"runs_this_month": 0,
"runs_in_progress": 0,
"activated_at": null,
"activate_at": "2026-11-02T12:00:00.000000Z",
"deactivate_at": "2026-11-10T02:59:00.000000Z",
"created_at": "2026-10-14T19:22:00.000000Z",
"updated_at": "2026-10-24T16:40:00.000000Z",
"issues": [],
"webhook_signing_secret": "whsec_YXV0b21hY2FvLWV4ZW1wbG8tYWJjZGVmMTIzNA==",
"experiments_included": true
}
#Cancelar la programación
DELETE/automations/{id}/schedule
Borra la activación y la pausa programadas (activate_at y deactivate_at vuelven a null). El estado no cambia. Devuelve la automatización completa.
Parámetros de ruta
idstringobligatorioID de la automatización.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo.
cURL
curl -X DELETE https://api.postal.so/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/schedule \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/schedule', {
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/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/schedule",
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('automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/schedule', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "automation",
"id": "019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f",
"paused_by_plan": false,
"name": "Boas-vindas do teste grátis",
"status": "draft",
"trigger": {
"type": "event",
"event": "trial.started",
"filters": [
{
"key": "plan",
"operator": "equals",
"value": "pro"
}
]
},
"reentry": "once",
"steps": [
{
"id": "welcome",
"type": "send_email",
"config": {
"template_id": "trial-welcome",
"from": "Ana da Loja Exemplo <[email protected]>",
"subject": null,
"reply_to": null,
"variables": {
"DAYS": "{{event.trial_days}}"
}
}
},
{
"id": "wait_2_days",
"type": "wait",
"config": {
"mode": "duration",
"amount": 2,
"unit": "days"
}
},
{
"id": "check_opened",
"type": "condition",
"config": {
"kind": "email_activity",
"step_id": "welcome",
"activity": "opened"
},
"branches": {
"yes": [
{
"id": "tag_engaged",
"type": "update_contact",
"config": {
"actions": [
{
"type": "add_to_segment",
"segment_id": "019a1d91-4c5d-7e6f-8a7b-8c9d0e1f2a3c"
}
]
}
}
],
"no": [
{
"id": "wait_morning",
"type": "wait",
"config": {
"mode": "until",
"time": "09:00",
"weekdays": [1, 2, 3, 4, 5],
"timezone": "America/Sao_Paulo"
}
},
{
"id": "reminder",
"type": "send_email",
"config": {
"template_id": "trial-help",
"from": null,
"subject": "{{contact.first_name}}, posso ajudar com o seu teste?",
"reply_to": null,
"variables": {}
}
},
{
"id": "notify_crm",
"type": "webhook",
"config": {
"url": "https://crm.lojaexemplo.com.br/hooks/postal"
}
}
]
}
}
],
"version": null,
"runs_this_month": 0,
"runs_in_progress": 0,
"activated_at": null,
"activate_at": null,
"deactivate_at": null,
"created_at": "2026-10-14T19:22:00.000000Z",
"updated_at": "2026-10-24T16:45:00.000000Z",
"issues": [],
"webhook_signing_secret": "whsec_YXV0b21hY2FvLWV4ZW1wbG8tYWJjZGVmMTIzNA==",
"experiments_included": true
}
#Resultados de los experimentos
GET/automations/{id}/experiments
Cada paso experiment de la definición actual, con la ganadora (si la hay) y, por variante y por el control: entered (contactos que entraron), emails_sent (correos enviados después de la división) y cuántos de esos contactos abrieron (opened, que incluye a quien hizo clic o respondió), hicieron clic (clicked), respondieron (replied) y convirtieron (converted, con goal_event). Cada contacto cuenta una vez en cada métrica.
Parámetros de ruta
idstringobligatorioID de la automatización.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo.
cURL
curl https://api.postal.so/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/experiments \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/experiments', {
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/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/experiments",
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('automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/experiments', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "list",
"data": [
{
"step_id": "subject_test",
"goal_event": "order.paid",
"winner": null,
"variants": [
{
"key": "a",
"name": "Assunto curto",
"control": false,
"percent": 45,
"entered": 412,
"emails_sent": 412,
"opened": 187,
"clicked": 41,
"replied": 3,
"converted": 22
},
{
"key": "b",
"name": "Assunto com o nome",
"control": false,
"percent": 45,
"entered": 398,
"emails_sent": 398,
"opened": 221,
"clicked": 58,
"replied": 5,
"converted": 31
},
{
"key": "control",
"name": null,
"control": true,
"percent": 10,
"entered": 93,
"emails_sent": 0,
"opened": 0,
"clicked": 0,
"replied": 0,
"converted": 4
}
]
}
]
}
#Declarar la ganadora
POST/automations/{id}/experiments/{step_id}/winner
Todos los contactos que lleguen al paso a partir de ahora van a la variante elegida (el control no puede ser la ganadora). Quien ya pasó sigue en su variante. Declarar de nuevo cambia la ganadora. Devuelve los resultados del experimento.
Parámetros de ruta
idstringobligatorioID de la automatización.step_idstringobligatorioEliddel pasoexperiment.
Cuerpo de la solicitud
variantstringobligatorioLa clave de la variante, comob.
Errores
- 422
validation_errorLa clave no pertenece a ninguna variante del experimento. - 403
paused_by_planLa automatización está pausada porque superó el límite de automatizaciones del plan. Mejora tu plan o elimina otras. - 404
not_foundEl recurso no existe o es de otro equipo.
cURL
curl -X POST https://api.postal.so/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/experiments/subject_test/winner \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"variant": "b"
}'
Node.js
const response = await fetch('https://api.postal.so/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/experiments/subject_test/winner', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
variant: 'b',
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/experiments/subject_test/winner",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"variant": "b",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/experiments/subject_test/winner', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'variant' => 'b',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "automation_experiment",
"step_id": "subject_test",
"goal_event": "order.paid",
"winner": {
"variant": "b",
"declared_at": "2026-10-30T15:04:00Z",
"declared_by": "Ana Souza"
},
"variants": [
{
"key": "a",
"name": "Assunto curto",
"control": false,
"percent": 45,
"entered": 412,
"emails_sent": 412,
"opened": 187,
"clicked": 41,
"replied": 3,
"converted": 22
},
{
"key": "b",
"name": "Assunto com o nome",
"control": false,
"percent": 45,
"entered": 398,
"emails_sent": 398,
"opened": 221,
"clicked": 58,
"replied": 5,
"converted": 31
},
{
"key": "control",
"name": null,
"control": true,
"percent": 10,
"entered": 93,
"emails_sent": 0,
"opened": 0,
"clicked": 0,
"replied": 0,
"converted": 4
}
]
}
#Ejecuciones
| Estado | Qué significa |
|---|---|
running | Ejecutando pasos ahora. |
waiting | Esperando hasta next_run_at: un paso wait, un wait_for (que se despierta antes si la acción ocurre) o un nuevo intento de webhook. current_step_id es el próximo paso. |
completed | Llegó al final de un camino o a un exit. |
failed | Un paso falló; el motivo está en error y en el registro del paso. |
canceled | Cancelada desde el panel o por la API. |
skipped | No empezó: se había agotado el límite mensual de ejecuciones del plan. |
Cada paso ejecutado queda registrado en steps, con status (running, waiting, completed, failed o skipped), intentos, error y una salida (output): {"until"} en una espera, {"email_id", "to", "subject"} en un envío, {"result": "yes"} en una condición, {"variant": "b", "reason": "split"} en un experimento (reason: split, winner o plan), {"changes": […]} en un cambio de contacto y {"status_code"} en un webhook. Un wait_for guarda {"email_id", "activity", "deadline", "resolved_by"} (consulta Esperar una acción en el correo), y un envío con detect_replies incluye además el reply_address. Los pasos que eligen un camino (condición, wait_for, experimento) incluyen también result, el camino seguido.
El trigger de la ejecución indica qué la inició: siempre el type y, según el disparador, event_id, event y properties (evento); segment_id; topic_id; tag_id; form_id y form_submission_id; email_id, template_id, broadcast_id y link (apertura y clic); o property, from y to (cambio de propiedad).
#Listar ejecuciones
GET/automations/{id}/runs
Las ejecuciones de la automatización, de la más reciente a la más antigua, con la definición de la versión que sigue cada una (definition). El registro de los pasos solo viene en obtener una ejecución.
Parámetros de ruta
idstringobligatorioID de la automatización.
Parámetros de consulta
limitintegerDe 1 a 100. Predeterminado: 20.afterstringCursor de la página siguiente.beforestringCursor de la página anterior.statusstringUno o más estados separados por comas, comorunning,waiting.contactstringID o correo electrónico del contacto.
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/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/runs?status=waiting,failed' \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/runs?status=waiting,failed', {
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/automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/runs",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
params={
"status": "waiting,failed",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->get('automations/019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f/runs', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'query' => [
'status' => 'waiting,failed',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "list",
"has_more": true,
"data": [
{
"object": "automation_run",
"id": "019a1f70-7182-7d94-9fa0-1b2c3d4e5f60",
"automation_id": "019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f",
"version": 2,
"contact_id": "019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"contact_email": "[email protected]",
"status": "waiting",
"trigger": {
"type": "event",
"event_id": "019a1f40-5f60-7b72-9d8e-9f0a1b2c3d4e",
"event": "trial.started",
"properties": {
"plan": "pro",
"source": "website",
"trial_days": 14
}
},
"current_step_id": "check_opened",
"next_run_at": "2026-10-26T14:20:02.000000Z",
"error": null,
"started_at": "2026-10-24T14:20:01.000000Z",
"finished_at": null,
"created_at": "2026-10-24T14:20:01.000000Z",
"definition": [
{
"id": "welcome",
"…": "…"
}
]
}
]
}
#Obtener una ejecución
GET/automation-runs/{id}
La ejecución con lo que la inició (trigger: el tipo y, según el disparador, el evento con las propiedades, el segmento o el formulario), la definición de la versión y el registro de cada paso.
Parámetros de ruta
idstringobligatorioID de la ejecución.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo.
cURL
curl https://api.postal.so/automation-runs/019a1f70-7182-7d94-9fa0-1b2c3d4e5f60 \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/automation-runs/019a1f70-7182-7d94-9fa0-1b2c3d4e5f60', {
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/automation-runs/019a1f70-7182-7d94-9fa0-1b2c3d4e5f60",
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('automation-runs/019a1f70-7182-7d94-9fa0-1b2c3d4e5f60', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "automation_run",
"id": "019a1f70-7182-7d94-9fa0-1b2c3d4e5f60",
"automation_id": "019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f",
"automation_name": "Boas-vindas do teste grátis",
"version": 2,
"contact_id": "019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"contact_email": "[email protected]",
"status": "waiting",
"trigger": {
"type": "event",
"event_id": "019a1f40-5f60-7b72-9d8e-9f0a1b2c3d4e",
"event": "trial.started",
"properties": {
"plan": "pro",
"source": "website",
"trial_days": 14
}
},
"current_step_id": "check_opened",
"next_run_at": "2026-10-26T14:20:02.000000Z",
"error": null,
"started_at": "2026-10-24T14:20:01.000000Z",
"finished_at": null,
"created_at": "2026-10-24T14:20:01.000000Z",
"definition": [
{
"id": "welcome",
"type": "send_email",
"config": {
"template_id": "trial-welcome",
"from": "Ana da Loja Exemplo <[email protected]>",
"subject": null,
"reply_to": null,
"variables": {
"DAYS": "{{event.trial_days}}"
}
}
},
{
"id": "wait_2_days",
"type": "wait",
"config": {
"mode": "duration",
"amount": 2,
"unit": "days"
}
},
{
"id": "check_opened",
"type": "condition",
"config": {
"kind": "email_activity",
"step_id": "welcome",
"activity": "opened"
},
"branches": {
"yes": [
{
"id": "tag_engaged",
"type": "update_contact",
"config": {
"actions": [
{
"type": "add_to_segment",
"segment_id": "019a1d91-4c5d-7e6f-8a7b-8c9d0e1f2a3c"
}
]
}
}
],
"no": [
{
"id": "wait_morning",
"type": "wait",
"config": {
"mode": "until",
"time": "09:00",
"weekdays": [1, 2, 3, 4, 5],
"timezone": "America/Sao_Paulo"
}
},
{
"id": "reminder",
"type": "send_email",
"config": {
"template_id": "trial-help",
"from": null,
"subject": "{{contact.first_name}}, posso ajudar com o seu teste?",
"reply_to": null,
"variables": {}
}
},
{
"id": "notify_crm",
"type": "webhook",
"config": {
"url": "https://crm.lojaexemplo.com.br/hooks/postal"
}
}
]
}
}
],
"steps": [
{
"object": "automation_run_step",
"id": "019a1f70-7183-7ea5-8ab1-2c3d4e5f6071",
"step_id": "welcome",
"type": "send_email",
"status": "completed",
"attempts": 1,
"output": {
"email_id": "019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b",
"to": "[email protected]",
"subject": "Ana, seu teste do plano Pro começou"
},
"error": null,
"started_at": "2026-10-24T14:20:01.000000Z",
"finished_at": "2026-10-24T14:20:02.000000Z"
},
{
"object": "automation_run_step",
"id": "019a1f70-7184-7fb6-9bc2-3d4e5f607182",
"step_id": "wait_2_days",
"type": "wait",
"status": "waiting",
"attempts": 1,
"output": {
"until": "2026-10-26T14:20:02Z"
},
"error": null,
"started_at": "2026-10-24T14:20:02.000000Z",
"finished_at": null
}
]
}
#Cancelar una ejecución
POST/automation-runs/{id}/cancel
Detiene una ejecución en curso o en espera; la espera pendiente queda skipped. Devuelve la ejecución.
Parámetros de ruta
idstringobligatorioID de la ejecución.
Errores
- 422
validation_errorLa ejecución ya terminó. - 404
not_foundEl recurso no existe o es de otro equipo.
cURL
curl -X POST https://api.postal.so/automation-runs/019a1f70-7182-7d94-9fa0-1b2c3d4e5f60/cancel \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/automation-runs/019a1f70-7182-7d94-9fa0-1b2c3d4e5f60/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/automation-runs/019a1f70-7182-7d94-9fa0-1b2c3d4e5f60/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('automation-runs/019a1f70-7182-7d94-9fa0-1b2c3d4e5f60/cancel', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "automation_run",
"id": "019a1f70-7182-7d94-9fa0-1b2c3d4e5f60",
"automation_id": "019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f",
"automation_name": "Boas-vindas do teste grátis",
"version": 2,
"contact_id": "019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"contact_email": "[email protected]",
"status": "canceled",
"trigger": {
"type": "event",
"event_id": "019a1f40-5f60-7b72-9d8e-9f0a1b2c3d4e",
"event": "trial.started",
"properties": {
"plan": "pro",
"source": "website",
"trial_days": 14
}
},
"current_step_id": "check_opened",
"next_run_at": null,
"error": "Canceled from the dashboard or API.",
"started_at": "2026-10-24T14:20:01.000000Z",
"finished_at": "2026-10-25T09:12:00.000000Z",
"created_at": "2026-10-24T14:20:01.000000Z",
"definition": [
{
"id": "welcome",
"type": "send_email",
"config": {
"template_id": "trial-welcome",
"from": "Ana da Loja Exemplo <[email protected]>",
"subject": null,
"reply_to": null,
"variables": {
"DAYS": "{{event.trial_days}}"
}
}
},
{
"id": "wait_2_days",
"type": "wait",
"config": {
"mode": "duration",
"amount": 2,
"unit": "days"
}
},
{
"id": "check_opened",
"type": "condition",
"config": {
"kind": "email_activity",
"step_id": "welcome",
"activity": "opened"
},
"branches": {
"yes": [
{
"id": "tag_engaged",
"type": "update_contact",
"config": {
"actions": [
{
"type": "add_to_segment",
"segment_id": "019a1d91-4c5d-7e6f-8a7b-8c9d0e1f2a3c"
}
]
}
}
],
"no": [
{
"id": "wait_morning",
"type": "wait",
"config": {
"mode": "until",
"time": "09:00",
"weekdays": [1, 2, 3, 4, 5],
"timezone": "America/Sao_Paulo"
}
},
{
"id": "reminder",
"type": "send_email",
"config": {
"template_id": "trial-help",
"from": null,
"subject": "{{contact.first_name}}, posso ajudar com o seu teste?",
"reply_to": null,
"variables": {}
}
},
{
"id": "notify_crm",
"type": "webhook",
"config": {
"url": "https://crm.lojaexemplo.com.br/hooks/postal"
}
}
]
}
}
],
"steps": [
{
"object": "automation_run_step",
"id": "019a1f70-7183-7ea5-8ab1-2c3d4e5f6071",
"step_id": "welcome",
"type": "send_email",
"status": "completed",
"attempts": 1,
"output": {
"email_id": "019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b",
"to": "[email protected]",
"subject": "Ana, seu teste do plano Pro começou"
},
"error": null,
"started_at": "2026-10-24T14:20:01.000000Z",
"finished_at": "2026-10-24T14:20:02.000000Z"
},
{
"object": "automation_run_step",
"id": "019a1f70-7184-7fb6-9bc2-3d4e5f607182",
"step_id": "wait_2_days",
"type": "wait",
"status": "skipped",
"attempts": 1,
"output": {
"until": "2026-10-26T14:20:02Z"
},
"error": null,
"started_at": "2026-10-24T14:20:02.000000Z",
"finished_at": "2026-10-25T09:12:00.000000Z"
}
]
}
#Paso de webhook
El paso webhook hace un POST con JSON a tu URL (HTTPS y pública), con el contacto y el evento que inició la ejecución:
{
"type": "automation.webhook",
"created_at": "2026-10-28T12:00:03.410Z",
"data": {
"automation_id": "019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f",
"automation_name": "Boas-vindas do teste grátis",
"run_id": "019a1f70-7182-7d94-9fa0-1b2c3d4e5f60",
"step_id": "notify_crm",
"trigger": "event",
"contact": {
"id": "019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"email": "[email protected]",
"first_name": "Ana",
"last_name": "Souza",
"unsubscribed": false,
"properties": {
"plan": "pro",
"city": "Recife"
},
"created_at": "2026-10-24T14:00:00.000Z",
"updated_at": "2026-10-24T14:00:00.000Z"
},
"event": {
"id": "019a1f40-5f60-7b72-9d8e-9f0a1b2c3d4e",
"name": "trial.started",
"properties": {
"plan": "pro",
"source": "website",
"trial_days": 14
}
}
}
}
La solicitud se firma como los webhooks de Postal, con las cabeceras webhook-id, webhook-timestamp y webhook-signature (y los equivalentes svix-*), pero usando el webhook_signing_secret de la automatización, que aparece en GET /automations/{id}. Sirve el mismo código de verificación. El webhook-id es el ID del registro del paso y se repite en los nuevos intentos, para que descartes duplicados. En data, trigger es el tipo del disparador (como event o tag.added), y event solo viene cuando la ejecución la inició un evento (si no, es null).
- Responde con
2xxen un máximo de 15 segundos. Las redirecciones no se siguen y las direcciones de red privada se rechazan. - Cualquier otra respuesta cuenta como fallo. Son 3 intentos: el segundo 1 minuto después, el tercero 10 minutos después. Pasado eso, el paso y la ejecución quedan
failed. - Las entregas usan el User-Agent
Postal-Webhooks/1.0.