Automações
Sequências de e-mails e ações disparadas por eventos, contatos novos, segmentos, formulários, etiquetas, aberturas e cliques, com testes A/B, versões, agendamento e o registro de cada execução.
Automações enviam e-mails e cuidam dos contatos sozinhas, a partir de um gatilho: um evento do seu produto, um contato novo, a entrada num segmento ou a saída dele, uma inscrição num tópico ou por formulário. Cada contato que dispara o gatilho ganha uma execução, que percorre os passos (esperar, enviar e-mail, verificar uma condição, mudar o contato, chamar um webhook) e registra o que aconteceu em cada um. Todos os endpoints exigem uma chave de acesso total.
Os gatilhos incluem ainda uma etiqueta que o contato recebe, a abertura ou o clique num e-mail e a mudança de um campo ou de uma propriedade do contato; entre os passos há esperar até o contato abrir, clicar ou responder um e-mail (wait_for) e dividir os contatos entre variantes num teste A/B (experiment). Uma automação pode ser ativada e pausada em horários agendados.
#Rascunho, ativa e pausada
| Status | Como funciona |
|---|---|
draft | Criada assim. Pode ser salva incompleta (um e-mail sem template, uma condição sem caminhos); o que falta aparece em issues. Não inicia execuções. |
active | Depois de ativar: a definição precisa estar completa e vira uma versão (version). Cada gatilho inicia uma execução. |
paused | Depois de pausar: não inicia execuções, e as que estão esperando ficam paradas até a automação ser ativada de novo. |
Versões. Editar uma automação ativa publica uma nova versão na hora (se o gatilho, os passos ou reentry mudaram). Só as novas execuções seguem a versão nova: as que já estão em andamento terminam na versão em que começaram (version da execução). Mudar só o nome não cria versão.
Reentrada. Com reentry: "once" (padrão), cada contato passa pela automação uma vez só. Com "every_time", cada gatilho inicia uma nova execução, mesmo que outra esteja em andamento.
Limite do plano. As execuções iniciadas no mês (em UTC), somando todas as automações, têm limite por plano (veja Limites e automation_runs em GET /usage). Passou do limite, a execução fica registrada como skipped, com o motivo, e não conta no limite.
Agendamento. POST /automations/{id}/schedule marca o horário em que a automação é ativada (activate_at) e/ou pausada (deactivate_at), por exemplo para uma promoção com começo e fim. Os horários aparecem na automação e são aplicados a cada minuto; depois de aplicado, o campo volta a null. Uma automação agendada para ativar precisa estar completa, como uma ativa.
Limites do plano. Além das execuções por mês, o plano pode limitar quantas automações o time tem (automations, que só vale na criação: criar além do limite responde 403 plan_limit_reached) e se inclui testes A/B (automation_experiments). Os números de cada plano estão em GET /plans. Quando o plano diminui e passa a ter menos automações do que o time tem, as mais novas acima do limite ficam com paused_by_plan: true: continuam listadas e legíveis, mas não iniciam execuções e editar, ativar, agendar ou declarar uma vencedora responde 403 paused_by_plan até o time voltar ao limite (fazendo upgrade ou apagando outras).
#Gatilho e passos
Uma automação é um trigger e uma lista de steps. Os passos rodam em ordem; uma condition fecha a lista e continua num dos caminhos (branches.yes ou branches.no), cada um com a própria lista; um experiment continua no caminho da variante sorteada (branches.a, branches.b… ou branches.control). O fim de uma lista termina a execução. Uma automação pode ter até 100 passos no total. Um wait_for também fecha a lista e continua em branches.yes ou branches.no.
Gatilhos
type | Campos | Quando inicia | Exemplo |
|---|---|---|---|
event | event: nome do evento. filters: até 10 comparações com as propriedades do evento, todas precisam bater. | Um evento com esse nome chega com um contato. | {"type": "event", "event": "order.paid", "filters": [{"key": "total", "operator": "gte", "value": 500}]} |
contact.created | — | Um contato é criado pela API, por um formulário ou por um evento com e-mail novo. Contatos de importação de CSV só disparam quando a importação pede (trigger_automations, veja Importar um CSV). | {"type": "contact.created"} |
segment.added | segment_id | Um contato entra no segmento: pela API, por um formulário ou por um passo update_contact, ou, num segmento dinâmico, quando passa a bater com os filtros (uma vez por entrada, também avisada pelo webhook contact.segment_entered). Salvar um segmento dinâmico não dispara nada para quem já bate com ele. Importações só disparam com trigger_automations. | {"type": "segment.added", "segment_id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a"} |
segment.removed | segment_id | Um contato sai do segmento: removido de um segmento estático (pela API, pelo painel ou por um passo update_contact) ou deixa de atender aos filtros de um segmento dinâmico quando ele é recalculado. Cada saída dispara uma vez (e o webhook contact.segment_exited avisa). Importações não disparam. | {"type": "segment.removed", "segment_id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a"} |
topic.subscribed | topic_id | A escolha do contato para o tópico passa a ser opt_in (antes não havia escolha ou era opt_out): na página de preferências, num formulário, pela API ou por uma automação. Importações e a própria automação que fez a mudança não disparam. | {"type": "topic.subscribed", "topic_id": "019a1d95-4c5d-7e6f-8a7b-8c9d0e1f2a3b"} |
topic.unsubscribed | topic_id | O contrário: a escolha passa a ser opt_out. | {"type": "topic.unsubscribed", "topic_id": "019a1d95-4c5d-7e6f-8a7b-8c9d0e1f2a3b"} |
form.submitted | form_id | Alguém se inscreve pelo formulário (com double opt-in, depois de confirmar). | {"type": "form.submitted", "form_id": "019a1da0-7f80-7b91-8c0d-2e3f4a5b6c7d"} |
tag.added | tag_id (ou tag_name, para uma etiqueta que ainda não existe: ela é criada ao salvar) | O contato recebe a etiqueta que ele ainda não tinha: pela API, pelo painel ou por um passo update_contact. Importações só disparam com trigger_automations. Veja Contatos. | {"type": "tag.added", "tag_name": "cliente-vip"} |
email.opened | Filtros opcionais: template_id, broadcast_id e automation_id; todos os informados precisam bater com o e-mail. | Um contato abre um e-mail. Um clique também conta como abertura, já que as aberturas não são medidas quando as imagens estão bloqueadas. O contato é o do e-mail de uma campanha ou automação ou, nos outros, o que tem o endereço do primeiro destinatário. Cada automação inicia no máximo uma execução para o mesmo contato e e-mail. | {"type": "email.opened", "broadcast_id": "019a1f00-2c3d-7e4f-8a5b-6c7d8e9f0a1b"} |
email.clicked | Os mesmos filtros de email.opened, mais link_contains: um trecho do link clicado (sem diferenciar maiúsculas, até 500 caracteres). | Um contato clica num link de um e-mail. Um clique inicia as automações de email.clicked e as de email.opened. | {"type": "email.clicked", "link_contains": "/checkout"} |
contact.property_changed | property: first_name, last_name, email, unsubscribed, tags ou a chave de uma propriedade de contato. Opcionais: from (valor de antes) e to (valor de depois); vazio vale para qualquer valor. | O campo ou a propriedade muda, por qualquer caminho (API, painel, formulário, evento com contact_properties ou outra automação). Um contato novo conta como mudança de vazio para cada valor com que nasceu. Em tags, to é a etiqueta adicionada e from, a removida. Textos são comparados sem diferenciar maiúsculas e números como números. A automação que fez a mudança, num passo update_contact, não se inicia de novo; importações nunca disparam. | {"type": "contact.property_changed", "property": "plan", "from": "free", "to": "pro"} |
Passos
Cada passo é {"id", "type", "config"}. O id (letras, números, _ e -, até 64 caracteres, único na automação) identifica o passo nas condições e no registro das execuções; sem ele, a Postal gera um como step_k3x9p2qa7m. Chaves desconhecidas são descartadas.
type | O que faz | config |
|---|---|---|
wait | Espera um tempo ou até um horário. | {"mode": "duration", "amount": 2, "unit": "days"} (unidades: minutes, hours, days, weeks; até 365 dias) ou {"mode": "until", "time": "09:00", "weekdays": [1, 2, 3, 4, 5], "timezone": "America/Sao_Paulo"}: o próximo 09:00, só nos dias da semana listados (1 = segunda, 7 = domingo; vazio = qualquer dia). Fuso padrão: America/Sao_Paulo. |
wait_for | Espera o contato abrir, clicar ou responder um e-mail enviado antes, até um prazo. | {"step_id": "welcome", "activity": "opened", "timeout": {"amount": 2, "unit": "days"}}. Segue por branches.yes assim que acontece ou por branches.no quando o prazo passa. Fica sempre no fim da sua lista. Veja Esperar uma ação no e-mail. |
send_email | Envia um template publicado para o contato. | template_id (ID ou alias, obrigatório), from (padrão: o do template), subject (no lugar do assunto do template), reply_to e variables. Veja Variáveis. Com detect_replies: true, a Postal detecta as respostas ao e-mail, o que wait_for e as condições de resposta precisam (veja Detectar respostas). |
condition | Segue pelo caminho yes ou no. | Fica sempre no fim da sua lista; os passos seguintes vão em branches.yes e branches.no. Veja Condições. |
update_contact | Muda o contato. | actions, até 20: {"type": "set_property", "key": "plan", "value": "pro"} (first_name, last_name ou uma propriedade; o valor aceita {{event.*}}), add_to_segment e remove_from_segment (com segment_id), subscribe_topic e unsubscribe_topic (com topic_id), unsubscribe e resubscribe. Também add_tag e remove_tag (com tag_id ou, para uma etiqueta que ainda não existe, tag_name: ela é criada ao salvar). Segmentos dinâmicos não aceitam add_to_segment nem remove_from_segment: só os estáticos. |
webhook | Envia o contato e o evento para uma URL sua. | {"url": "https://…"}. Veja Passo de webhook. |
experiment | Divide os contatos entre variantes (teste A/B). | De 2 a 4 variants, cada uma um caminho em branches, e um grupo de controle opcional. Fica sempre no fim da sua lista. Veja Experimentos. |
exit | Termina a execução. | Sem configuração; fica no fim da sua lista. O fim de uma lista já termina a execução, então use exit só para deixar claro. |
Condições
kind | Campos | Exemplo |
|---|---|---|
contact_property | key (email, first_name, last_name ou uma propriedade), operator, value | {"kind": "contact_property", "key": "plan", "operator": "equals", "value": "pro"} |
event_property | key (caminho na propriedade do evento que iniciou a execução, como items.0.name), operator, value | {"kind": "event_property", "key": "total", "operator": "gt", "value": 200} |
segment | segment_id, operator: in ou not_in | {"kind": "segment", "segment_id": "019a1d90-3b4c-7d5e-9f6a-7b8c9d0e1f2a", "operator": "in"} |
topic | topic_id, operator: subscribed ou not_subscribed | {"kind": "topic", "topic_id": "019a1d95-4c5d-7e6f-8a7b-8c9d0e1f2a3b", "operator": "subscribed"} |
tag | tag_id (ou tag_name), operator: has ou not_has | {"kind": "tag", "tag_id": "019a1d85-6c7d-7e8f-9a0b-1c2d3e4f5a6b", "operator": "has"} |
email_activity | step_id de um send_email anterior no mesmo caminho, activity: opened ou clicked. Um clique conta como abertura. Também aceita clicked_link (com link_contains, um trecho do link clicado) e replied (o e-mail precisa ter detect_replies). Uma resposta que não seja automática também conta como abertura. | {"kind": "email_activity", "step_id": "welcome", "activity": "opened"} |
Operadores de comparação (nas condições de propriedade e nos filtros do gatilho): equals, not_equals, contains, not_contains, starts_with, ends_with, gt, gte, lt, lte, is_set e is_not_set (esses dois sem value). Textos são comparados sem diferenciar maiúsculas; números, como números; datas no formato AAAA-MM-DD, em ordem de calendário.
Esperar uma ação no e-mail
O passo wait_for segura a execução até o contato abrir, clicar ou responder a um e-mail enviado por um send_email anterior, no mesmo caminho. Se a ação acontece, a execução segue na hora por branches.yes; se o timeout passa antes, segue por branches.no. É melhor que uma espera seguida de uma condição de atividade: reage assim que a pessoa age, em vez de olhar só no fim do prazo.
{
"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 | O que é |
|---|---|
step_id | O id de um send_email anterior no mesmo caminho. Obrigatório. |
activity | opened (padrão; um clique ou uma resposta também contam), clicked, clicked_link ou replied. |
link_contains | Só com clicked_link: um trecho do link clicado, sem diferenciar maiúsculas, até 500 caracteres. |
timeout | {"amount": 2, "unit": "days"}: quanto esperar, no máximo (minutes, hours, days ou weeks; até 365 dias). Obrigatório. |
repliedexigedetect_replies: truenosend_email; sem isso a automação não pode ser ativada. Respostas automáticas (fora do escritório) não contam.- Se o e-mail do
step_idnão saiu nesta execução (por exemplo, o contato está descadastrado e o envio foi pulado), a execução segue porbranches.no. - No registro da execução, o
outputdiz como terminou:resolved_byéevent(comresolved_at),timeoutouno_email.
Variáveis nos e-mails
O send_email usa a versão publicada do template, como um POST /emails com template: mesmas validações, cota de envio e lista de supressão. As variáveis do template ({{{NAME}}}) são preenchidas sozinhas com:
EMAIL,FIRST_NAME,LAST_NAMEeCONTACT_IDdo contato;- cada propriedade do contato e cada propriedade de primeiro nível do evento, pela chave e em maiúsculas (
planePLAN); POSTAL_UNSUBSCRIBE_URL, o link da página de preferências do contato;- e as
variablesdo passo, que aceitam referências como{{contact.first_name}},{{contact.plan}}e{{event.items.0.name}}(valores ausentes ficam vazios).
O subject do passo e os valores de set_property aceitam as mesmas referências {{contact.*}} e {{event.*}}. Os e-mails levam os cabeçalhos List-Unsubscribe com clique único; inclua {{{POSTAL_UNSUBSCRIBE_URL}}} no template para mostrar o link no corpo. Contatos descadastrados (unsubscribed) não recebem: o passo fica skipped e a execução continua. Se o envio falhar (cota esgotada, domínio sem verificação, variável sem valor), o passo e a execução ficam failed.
#Experimentos (teste A/B)
O passo experiment divide os contatos que chegam a ele entre 2 e 4 variantes, pela porcentagem de cada uma, para comparar assuntos, conteúdos ou esperas. Cada variante é um caminho em branches, com a chave da variante (letras minúsculas, números e _, como a e b). Com control_percent acima de 0, essa parte dos contatos vai para o caminho control, que pode ficar vazio: esses contatos pulam as variantes e terminam ali. As porcentagens das variantes mais a do controle somam 100. As chaves começam com letra, têm até 32 caracteres e não podem ser control; uma variante sem chave ganha a próxima letra livre (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": []
}
}
- Sempre a mesma variante. A variante sai de um sorteio determinístico pela execução e pelo passo, e fica gravada: a execução sempre segue o mesmo caminho, e cada execução aparece com
{"variant", "reason"}no registro do passo. - Conversão. Com
goal_event, um evento com esse nome enviado pelo contato depois de entrar na variante conta como conversão. - Resultados.
GET /automations/{id}/experimentstraz, por variante, quantos contatos entraram, os e-mails enviados depois da divisão e quantos desses contatos abriram, clicaram, responderam (respostas automáticas não contam) e converteram. - Vencedora.
POST /automations/{id}/experiments/{step_id}/winnermanda todos os próximos contatos para a variante escolhida, em qualquer versão da automação, e registra quem declarou e quando. Quem já passou continua na variante que recebeu. - Mudar a divisão. As variantes e as porcentagens de um experimento só mudam com a automação pausada (numa ativa, a resposta é
422). Nomes e os passos dentro das variantes podem mudar a qualquer momento. - Plano. Experimentos dependem do recurso
automation_experimentsdo plano. Sem ele, criar, salvar, ativar ou agendar uma automação com experimento responde403 plan_limit_reached. Se o time perde o recurso com a automação ativa, ela continua rodando e todos os contatos vão para a primeira variante. O detalhe da automação trazexperiments_included.
#Listar automações
GET/automations
Cada automação com a definição, a versão em vigor (null num rascunho nunca ativado), runs_this_month (execuções iniciadas no mês) e runs_in_progress.
Parâmetros de consulta
limitintegerDe 1 a 100. Padrão: 20.afterstringCursor da próxima página.beforestringCursor da página anterior.searchstringBusca no nome.statusstringdraft,activeoupaused.scheduledbooleanComtrue, só as automações com uma ativação ou uma pausa agendada.
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"
}
]
}
#Criar uma automação
POST/automations
Cria um rascunho. Erros de estrutura recusam o salvamento (um tipo de passo desconhecido, um passo depois de uma condição, um segmento de outro time, um ID repetido); o que está só faltando entra em issues e impede apenas a ativação. Devolve a automação completa, com issues e o webhook_signing_secret usado nos passos de webhook.
Corpo da requisição
Erros
- 422
validation_errorCampo inválido ou definição com erro. O campo extraerrorslista cada problema, com ostep_id(ounull, para o gatilho) e a mensagem. - 403
plan_limit_reachedA definição tem um experimento e o plano não inclui testes A/B. - 403
plan_limit_reachedO plano não permite mais automações (limiteautomations).
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."
}
]
}
#Obter uma automação
GET/automations/{id}
Devolve a automação com issues (o que impede a ativação, cada um com step_id e message) e o webhook_signing_secret.
Parâmetros de caminho
idstringobrigatórioID da automação.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
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
}
#Atualizar uma automação
PATCH/automations/{id}
Só os campos enviados mudam; trigger e steps substituem os anteriores por inteiro. Num rascunho ou numa automação pausada, a definição pode ficar incompleta. Numa automação ativa (ou com uma ativação agendada), ela precisa estar completa, e a mudança numa ativa vira uma nova versão, que vale só para as novas execuções. Devolve a automação completa.
Parâmetros de caminho
idstringobrigatórioID da automação.
Corpo da requisição (todos opcionais)
namestringNovo nome.triggerobjectNovo gatilho.stepsobject[]Nova lista de passos.reentrystringonceouevery_time.
Erros
- 422
validation_errorDefinição com erro ou, numa automação ativa, incompleta ou com a divisão de um experimento alterada. Vejaerrors. - 403
plan_limit_reachedA definição tem um experimento e o plano não inclui testes A/B. - 403
paused_by_planA automação está pausada porque passou do limite de automações do plano. Faça upgrade ou apague outras. - 404
not_foundO recurso não existe ou é de outro time.
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
}
#Excluir uma automação
DELETE/automations/{id}
Apaga a automação com as versões e as execuções; as que estavam em andamento param. Para só interromper, prefira pausar.
Parâmetros de caminho
idstringobrigatórioID da automação.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
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
}
#Ativar
POST/automations/{id}/activate
Ativa um rascunho ou retoma uma automação pausada. A definição precisa estar completa: templates escolhidos e publicados, remetente de domínio verificado, caminhos das condições com ao menos um passo, URLs dos webhooks… Publica a definição como versão (se mudou desde a última) e devolve a automação.
Parâmetros de caminho
idstringobrigatórioID da automação.
Erros
- 422
validation_errorA definição tem erros ou ainda está incompleta.errorslista tudo o que falta. - 403
plan_limit_reachedA automação tem um experimento e o plano não inclui testes A/B. - 403
paused_by_planA automação está pausada porque passou do limite de automações do plano. Faça upgrade ou apague outras. - 404
not_foundO recurso não existe ou é de outro time.
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
Para de iniciar execuções. As que estão esperando ficam paradas e continuam de onde estavam quando a automação for ativada de novo; uma espera que venceu nesse meio-tempo segue logo depois.
Parâmetros de caminho
idstringobrigatórioID da automação.
Erros
- 422
validation_errorA automação não está ativa. - 404
not_foundO recurso não existe ou é de outro time.
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
}
#Agendar a ativação ou a pausa
POST/automations/{id}/schedule
Agenda quando a automação é ativada (activate_at) e/ou pausada (deactivate_at), úteis para uma campanha com começo e fim. Mande ao menos um dos dois; um campo que você não manda mantém o valor atual, e null limpa. Os horários são aplicados a cada minuto, e cada um volta a null depois de aplicado. Devolve a automação completa.
Uma ativação só pode ser agendada para uma automação que ainda não está ativa e que está completa (a mesma exigência de ativar; se ela deixar de estar completa até o horário, a ativação é descartada e a automação fica como está). Uma pausa precisa de uma automação ativa ou de uma ativação agendada antes dela.
Parâmetros de caminho
idstringobrigatórioID da automação.
Corpo da requisição
activate_atstring | nullQuando ativar, em ISO 8601 com fuso, como2026-11-02T09:00:00-03:00. No futuro e até um ano à frente.deactivate_atstring | nullQuando pausar, no mesmo formato. Precisa ser depois deactivate_at, se os dois forem mandados.
Erros
- 422
validation_errorNenhum dos dois campos, horário que não é uma data no futuro ou passa de um ano,deactivate_atantes deactivate_at, ativação de uma automação já ativa, pausa sem ativação numa automação que não está ativa, ou definição incompleta (errorslista o que falta). - 403
plan_limit_reachedA automação tem um experimento e o plano não inclui testes A/B. - 403
paused_by_planA automação está pausada porque passou do limite de automações do plano. Faça upgrade ou apague outras. - 404
not_foundO recurso não existe ou é de outro time.
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 o agendamento
DELETE/automations/{id}/schedule
Limpa a ativação e a pausa agendadas (activate_at e deactivate_at voltam a null). O status não muda. Devolve a automação completa.
Parâmetros de caminho
idstringobrigatórioID da automação.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
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 dos experimentos
GET/automations/{id}/experiments
Cada passo experiment da definição atual, com a vencedora (se houver) e, por variante e pelo controle: entered (contatos que entraram), emails_sent (e-mails enviados depois da divisão) e quantos desses contatos abriram (opened, que inclui quem clicou ou respondeu), clicaram (clicked), responderam (replied) e converteram (converted, com goal_event). Cada contato conta uma vez em cada métrica.
Parâmetros de caminho
idstringobrigatórioID da automação.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
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 a vencedora
POST/automations/{id}/experiments/{step_id}/winner
Todos os contatos que chegarem ao passo a partir de agora vão para a variante escolhida (o controle não pode ser a vencedora). Quem já passou continua na sua variante. Declarar de novo troca a vencedora. Devolve os resultados do experimento.
Parâmetros de caminho
idstringobrigatórioID da automação.step_idstringobrigatórioOiddo passoexperiment.
Corpo da requisição
variantstringobrigatórioA chave da variante, comob.
Erros
- 422
validation_errorA chave não é de uma variante do experimento. - 403
paused_by_planA automação está pausada porque passou do limite de automações do plano. Faça upgrade ou apague outras. - 404
not_foundO recurso não existe ou é de outro time.
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
}
]
}
#Execuções
| Status | O que significa |
|---|---|
running | Executando passos agora. |
waiting | Esperando até next_run_at: um passo wait, um wait_for (que acorda antes se a ação acontece) ou uma nova tentativa de webhook. current_step_id é o próximo passo. |
completed | Chegou ao fim de um caminho ou a um exit. |
failed | Um passo falhou; o motivo está em error e no registro do passo. |
canceled | Cancelada pelo painel ou pela API. |
skipped | Não começou: o limite mensal de execuções do plano tinha acabado. |
Cada passo executado fica registrado em steps, com status (running, waiting, completed, failed ou skipped), tentativas, erro e uma saída (output): {"until"} numa espera, {"email_id", "to", "subject"} num envio, {"result": "yes"} numa condição, {"variant": "b", "reason": "split"} num experimento (reason: split, winner ou plan), {"changes": […]} numa mudança de contato e {"status_code"} num webhook. Um wait_for guarda {"email_id", "activity", "deadline", "resolved_by"} (veja Esperar uma ação no e-mail), e um envio com detect_replies traz também o reply_address. Passos que escolhem um caminho (condição, wait_for, experimento) trazem ainda result, o caminho seguido.
O trigger da execução diz o que a iniciou: sempre o type e, conforme o gatilho, event_id, event e properties (evento); segment_id; topic_id; tag_id; form_id e form_submission_id; email_id, template_id, broadcast_id e link (abertura e clique); ou property, from e to (mudança de propriedade).
#Listar execuções
GET/automations/{id}/runs
As execuções da automação, da mais recente para a mais antiga, com a definição da versão que cada uma segue (definition). O registro dos passos só vem em obter uma execução.
Parâmetros de caminho
idstringobrigatórioID da automação.
Parâmetros de consulta
limitintegerDe 1 a 100. Padrão: 20.afterstringCursor da próxima página.beforestringCursor da página anterior.statusstringUm ou mais status separados por vírgula, comorunning,waiting.contactstringID ou e-mail do contato.
Erros
- 422
validation_errorAlgum campo é inválido. A mensagem diz qual. - 404
not_foundO recurso não existe ou é de outro time.
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",
"…": "…"
}
]
}
]
}
#Obter uma execução
GET/automation-runs/{id}
A execução com o que a iniciou (trigger: o tipo e, conforme o gatilho, o evento com as propriedades, o segmento ou o formulário), a definição da versão e o registro de cada passo.
Parâmetros de caminho
idstringobrigatórioID da execução.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
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 uma execução
POST/automation-runs/{id}/cancel
Para uma execução em andamento ou esperando; a espera pendente fica skipped. Devolve a execução.
Parâmetros de caminho
idstringobrigatórioID da execução.
Erros
- 422
validation_errorA execução já terminou. - 404
not_foundO recurso não existe ou é de outro time.
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"
}
]
}
#Passo de webhook
O passo webhook faz um POST com JSON para a sua URL (HTTPS e pública), com o contato e o evento que iniciou a execução:
{
"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
}
}
}
}
A requisição é assinada como os webhooks da Postal, com os cabeçalhos webhook-id, webhook-timestamp e webhook-signature (e os equivalentes svix-*), mas usando o webhook_signing_secret da automação, que aparece em GET /automations/{id}. O mesmo código de verificação serve. O webhook-id é o ID do registro do passo e se repete nas novas tentativas, para você descartar duplicatas. Em data, trigger é o tipo do gatilho (como event ou tag.added), e event só vem quando a execução foi iniciada por um evento (senão é null).
- Responda com
2xxem até 15 segundos. Redirecionamentos não são seguidos e endereços de rede privada são recusados. - Qualquer outra resposta conta como falha. São 3 tentativas: a segunda 1 minuto depois, a terceira 10 minutos depois. Depois disso, o passo e a execução ficam
failed. - As entregas usam o User-Agent
Postal-Webhooks/1.0.