Iniciar sesión Crear cuenta
Marketing

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

EstadoCómo funciona
draftSe crea así. Se puede guardar incompleta (un correo sin plantilla, una condición sin caminos); lo que falta aparece en issues. No inicia ejecuciones.
activeDespués de activarla: la definición debe estar completa y se convierte en una versión (version). Cada disparador inicia una ejecución.
pausedDespué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

typeCamposCuándo se iniciaEjemplo
eventevent: 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.addedsegment_idUn 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.removedsegment_idUn 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.subscribedtopic_idLa 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.unsubscribedtopic_idLo contrario: la elección pasa a ser opt_out.{"type": "topic.unsubscribed", "topic_id": "019a1d95-4c5d-7e6f-8a7b-8c9d0e1f2a3b"}
form.submittedform_idAlguien se suscribe mediante el formulario (con doble opt-in, después de confirmar).{"type": "form.submitted", "form_id": "019a1da0-7f80-7b91-8c0d-2e3f4a5b6c7d"}
tag.addedtag_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.openedFiltros 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.clickedLos 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_changedproperty: 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.

typeQué haceconfig
waitEspera 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_forEspera 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_emailEnví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).
conditionSigue 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_contactModifica 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.
webhookEnvía el contacto y el evento a una URL tuya.{"url": "https://…"}. Consulta Paso de webhook.
experimentDivide 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.
exitTermina 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

kindCamposEjemplo
contact_propertykey (email, first_name, last_name o una propiedad), operator, value{"kind": "contact_property", "key": "plan", "operator": "equals", "value": "pro"}
event_propertykey (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}
segmentsegment_id, operator: in o not_in{"kind": "segment", "segment_id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a", "operator": "in"}
topictopic_id, operator: subscribed o not_subscribed{"kind": "topic", "topic_id": "019a1d95-4c5d-7e6f-8a7b-8c9d0e1f2a3b", "operator": "subscribed"}
tagtag_id (o tag_name), operator: has o not_has{"kind": "tag", "tag_id": "019a1d85-6c7d-7e8f-9a0b-1c2d3e4f5a6b", "operator": "has"}
email_activitystep_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.

Paso wait_for
{
  "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?"
        }
      }
    ]
  }
}
configQué es
step_idEl id de un send_email anterior en el mismo camino. Obligatorio.
activityopened (predeterminado; un clic o una respuesta también cuentan), clicked, clicked_link o replied.
link_containsSolo 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.
  • replied exige detect_replies: true en el send_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_id no salió en esta ejecución (por ejemplo, el contacto se dio de baja y se omitió el envío), la ejecución sigue por branches.no.
  • En el registro de la ejecución, el output indica cómo terminó: resolved_by es event (con resolved_at), timeout o no_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_NAME y CONTACT_ID del contacto;
  • cada propiedad del contacto y cada propiedad de primer nivel del evento, por su clave y en mayúsculas (plan y PLAN);
  • POSTAL_UNSUBSCRIBE_URL, el enlace a la página de preferencias del contacto;
  • y las variables del 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…).

Paso experiment
{
  "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}/experiments devuelve, 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}/winner enví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_experiments del plan. Sin él, crear, guardar, activar o programar una automatización con un experimento responde 403 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 incluye experiments_included.

#Listar automatizaciones

GET/automations

Clave de acceso total

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

  • limitinteger
    De 1 a 100. Predeterminado: 20.
  • afterstring
    Cursor de la página siguiente.
  • beforestring
    Cursor de la página anterior.
  • searchstring
    Búsqueda por nombre.
  • statusstring
    draft, active o paused.
  • scheduledboolean
    Con true, solo las automatizaciones con una activación o una pausa programada.
GET /automations

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);
200Respuesta
{
  "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

Clave de acceso total

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

  • namestringobligatorio
    Nombre, hasta 255 caracteres.
  • triggerobjectobligatorio
    El disparador, con type.
  • stepsobject[]
    Los pasos. Puede quedar vacío en un borrador.
  • reentrystring
    once (predeterminado) o every_time.

Errores

  • 422validation_error
    Campo no válido o definición con errores. El campo extra errors enumera cada problema, con el step_id (o null, para el disparador) y el mensaje.
  • 403plan_limit_reached
    La definición tiene un experimento y el plan no incluye pruebas A/B.
  • 403plan_limit_reached
    El plan no permite más automatizaciones (límite automations).
POST /automations

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);
201Respuesta
{
  "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
}
422Respuesta de error
{
  "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}

Clave de acceso total

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

  • idstringobligatorio
    ID de la automatización.

Errores

  • 404not_found
    El recurso no existe o es de otro equipo.
GET /automations/{id}

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);
200Respuesta
{
  "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}

Clave de acceso total

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

  • idstringobligatorio
    ID de la automatización.

Cuerpo de la solicitud (todos opcionales)

  • namestring
    Nombre nuevo.
  • triggerobject
    Disparador nuevo.
  • stepsobject[]
    Nueva lista de pasos.
  • reentrystring
    once o every_time.

Errores

  • 422validation_error
    Definición con errores o, en una automatización activa, incompleta o con la división de un experimento modificada. Consulta errors.
  • 403plan_limit_reached
    La definición tiene un experimento y el plan no incluye pruebas A/B.
  • 403paused_by_plan
    La automatización está pausada porque superó el límite de automatizaciones del plan. Mejora tu plan o elimina otras.
  • 404not_found
    El recurso no existe o es de otro equipo.
PATCH /automations/{id}

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);
200Respuesta
{
  "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}

Clave de acceso total

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

  • idstringobligatorio
    ID de la automatización.

Errores

  • 404not_found
    El recurso no existe o es de otro equipo.
DELETE /automations/{id}

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);
200Respuesta
{
  "object": "automation",
  "id": "019a1f60-6071-7c83-8e9f-0a1b2c3d4e5f",
  "deleted": true
}

#Activar

POST/automations/{id}/activate

Clave de acceso total

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

  • idstringobligatorio
    ID de la automatización.

Errores

  • 422validation_error
    La definición tiene errores o aún está incompleta. errors enumera todo lo que falta.
  • 403plan_limit_reached
    La automatización tiene un experimento y el plan no incluye pruebas A/B.
  • 403paused_by_plan
    La automatización está pausada porque superó el límite de automatizaciones del plan. Mejora tu plan o elimina otras.
  • 404not_found
    El recurso no existe o es de otro equipo.
POST /automations/{id}/activate

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);
200Respuesta
{
  "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
}
422Respuesta de error
{
  "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

Clave de acceso total

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

  • idstringobligatorio
    ID de la automatización.

Errores

  • 422validation_error
    La automatización no está activa.
  • 404not_found
    El recurso no existe o es de otro equipo.
POST /automations/{id}/pause

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);
200Respuesta
{
  "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

Clave de acceso total

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

  • idstringobligatorio
    ID de la automatización.

Cuerpo de la solicitud

  • activate_atstring | null
    Cuándo activar, en ISO 8601 con zona horaria, como 2026-11-02T09:00:00-03:00. En el futuro y hasta un año adelante.
  • deactivate_atstring | null
    Cuándo pausar, en el mismo formato. Debe ser posterior a activate_at, si se envían los dos.

Errores

  • 422validation_error
    Ninguno de los dos campos, hora que no es una fecha futura o supera un año, deactivate_at antes de activate_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 (errors enumera lo que falta).
  • 403plan_limit_reached
    La automatización tiene un experimento y el plan no incluye pruebas A/B.
  • 403paused_by_plan
    La automatización está pausada porque superó el límite de automatizaciones del plan. Mejora tu plan o elimina otras.
  • 404not_found
    El recurso no existe o es de otro equipo.
POST /automations/{id}/schedule

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);
200Respuesta
{
  "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

Clave de acceso total

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

  • idstringobligatorio
    ID de la automatización.

Errores

  • 404not_found
    El recurso no existe o es de otro equipo.
DELETE /automations/{id}/schedule

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);
200Respuesta
{
  "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

Clave de acceso total

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

  • idstringobligatorio
    ID de la automatización.

Errores

  • 404not_found
    El recurso no existe o es de otro equipo.
GET /automations/{id}/experiments

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);
200Respuesta
{
  "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

Clave de acceso total

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

  • idstringobligatorio
    ID de la automatización.
  • step_idstringobligatorio
    El id del paso experiment.

Cuerpo de la solicitud

  • variantstringobligatorio
    La clave de la variante, como b.

Errores

  • 422validation_error
    La clave no pertenece a ninguna variante del experimento.
  • 403paused_by_plan
    La automatización está pausada porque superó el límite de automatizaciones del plan. Mejora tu plan o elimina otras.
  • 404not_found
    El recurso no existe o es de otro equipo.
POST /automations/{id}/experiments/{step_id}/winner

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);
200Respuesta
{
  "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

EstadoQué significa
runningEjecutando pasos ahora.
waitingEsperando 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.
completedLlegó al final de un camino o a un exit.
failedUn paso falló; el motivo está en error y en el registro del paso.
canceledCancelada desde el panel o por la API.
skippedNo 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

Clave de acceso total

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

  • idstringobligatorio
    ID de la automatización.

Parámetros de consulta

  • limitinteger
    De 1 a 100. Predeterminado: 20.
  • afterstring
    Cursor de la página siguiente.
  • beforestring
    Cursor de la página anterior.
  • statusstring
    Uno o más estados separados por comas, como running,waiting.
  • contactstring
    ID o correo electrónico del contacto.

Errores

  • 422validation_error
    Algún campo no es válido. El mensaje indica cuál.
  • 404not_found
    El recurso no existe o es de otro equipo.
GET /automations/{id}/runs

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);
200Respuesta (definition abreviado)
{
  "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}

Clave de acceso total

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

  • idstringobligatorio
    ID de la ejecución.

Errores

  • 404not_found
    El recurso no existe o es de otro equipo.
GET /automation-runs/{id}

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);
200Respuesta
{
  "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

Clave de acceso total

Detiene una ejecución en curso o en espera; la espera pendiente queda skipped. Devuelve la ejecución.

Parámetros de ruta

  • idstringobligatorio
    ID de la ejecución.

Errores

  • 422validation_error
    La ejecución ya terminó.
  • 404not_found
    El recurso no existe o es de otro equipo.
POST /automation-runs/{id}/cancel

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);
200Respuesta
{
  "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:

automation.webhook
{
  "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 2xx en 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.