Automations turn hub events into actions. Instead of a single linear drip, you build a flowchart: a trigger starts a run, and each contact moves through nodes — send an email, wait, branch on a condition, add or remove a tag, hand off to a drip sequence, call a webhook, or exit.
A drip sequence becomes one node inside an automation. A one-step automation (one trigger, one action) is a “rule”. Same engine for both.
How it works
- Triggers subscribe an automation to an event type (e.g. a member joining, a purchase, a tag being applied, or a custom event you fire yourself).
- Each matching contact gets an enrollment — an independent run through the published graph. Enrollments are idempotent (one event enrolls a contact at most once) and honor a per-automation re-entry policy (
never, after_exit, interval).
- A published version is an immutable snapshot. Editing a draft and re-publishing creates a new version; in-flight enrollments keep running on the version they started on.
Node types
Authoring lifecycle
Automations are hub-scoped and require a team owner. The lifecycle is draft → publish (snapshot + validate) → activate.
To restore an editable draft after an Admin reload, read the single automation
resource. Its data.attributes.definition contains the saved graph while the
automation is in draft status. The field is null when there is no stored
draft graph or the automation is no longer a draft. The list response omits the
graph, and the internal settings._draft_definition storage key is never
returned in settings.
Save draft edits through the existing PATCH .../automations/{id} request with
attributes.definition. Published graphs are immutable revisions returned by
the existing GET .../automations/{id}/versions collection; there is no
single-version GET endpoint.
A create body carries the graph in attributes.definition:
publish validates the graph (reachability, branch edges must be labeled, condition trees compile against your tags/segments) and returns a versioned snapshot. Branch tag_slug values are resolved to IDs at publish time, so the referenced tags must already exist.
A draft can be saved before it has any nodes: create and PATCH accept definition.nodes: []. Publishing still requires at least one node. If the draft is empty, publish returns a JSON:API 422 with errors[0].source.pointer set to /data/attributes/definition/nodes; no version is published. Add a node and retry.
add_tag / remove_tag node configs take a raw tag_id (a tag’s UUID — copy it from mio tags list), not a tag_slug: the config schema is { "tag_id": "<uuid>" } and rejects any other key ({"tag_slug": …} → “Extra inputs are not permitted”). Only branch has_tag conditions (and segment conditions) accept a tag_slug, which is resolved to an id at publish time.
Most enrollments happen automatically when a trigger event fires. You can also enroll manually, or fire your own custom event from an integration.
Custom events require an idempotency_key (deduplicated per team) and identify the contact:
Returns 202 on a new event, 200 on a duplicate. Use /test to dry-run a contact through the published graph: it runs the real branch logic and reports the step trace without sending email, applying tags, or firing webhooks.
/test is the safe way to confirm routing before you turn an automation on — branches evaluate for real, but every side effect is stubbed.
Webhook endpoints
The webhook node POSTs a signed JSON payload to an endpoint you register. Targets are validated against an SSRF guard (private, loopback, link-local, and cloud-metadata addresses are rejected), the signing secret is encrypted at rest and never returned, and delivery is asynchronous with exponential-backoff retries.
Reliability
- Triggers are delivered from a durable event outbox, so a server hiccup never silently drops an enrollment.
- Each node executes at most once per enrollment (idempotency keys); failed steps retry, then move to a dead-letter queue rather than looping.
wait is real scheduling — a run resumes when its timer is due, even days later.