Automations
An automation is a graph: one trigger event, then conditions and actions wired together by edges.
Shape
{
"name": "Welcome new signups",
"trigger_event": "contact.created",
"graph": {
"nodes": [
{ "id": "t1", "type": "trigger" },
{ "id": "c1", "type": "condition",
"settings": { "conditions": [
{ "path": "data.properties.plan", "operator": "equals", "value": "pro" }
]}},
{ "id": "a1", "type": "action",
"settings": { "type": "send_email", "template_id": "…" } }
],
"edges": [
{ "from": "t1", "to": "c1" },
{ "from": "c1", "to": "a1", "branch": "true" }
]
}
}Execution walks breadth-first from the single trigger node. Condition nodes route to their true or false edges; action nodes run and fan out over all outgoing edges. A diamond join runs once — revisits are suppressed — and a failing action does not stop the nodes downstream of it.
Actions
| Type | What it does |
|---|---|
send_email | Renders a template and sends it |
send_channel_message | Posts to a configured Discord or Telegram channel |
call_url | Makes an HTTP request |
String settings support {{ dot.path }} placeholders resolved against the triggering event, so {{ data.values.email }} reaches a form submission's email field.
The loop guard
Events caused by an automation carry the originating automation's id, and the engine skips those. That is what makes it safe to trigger an automation on email.delivered without it feeding itself forever.
Validation
Graphs are validated when saved — a single trigger, correct branch edges, reachability, no cycles, and size caps — and validated again defensively when executed.
Seeing what happened
curl http://api.localhost/v1/automations/$ID/runs -H "X-API-Key: $KEY"A run is recorded whenever an action node was reached. Its detail carries one entry per visited node with the node's id, type, status and reason — so a run that did nothing tells you which condition sent it down the false branch.
Aggregate counts are at /v1/automations/metrics.