Developer Platform
Docs chevron_right Developer Platform chevron_right Trigger events API

Trigger events API

Trigger events are a named event vocabulary you define — order.completed, trial.started, anything your application knows about. Fire one via the API and every active automation listening for it enrolls the contact, with the fire's payload available inside the flow's emails as merge tags. Requires the triggers scope.

How it fits together

Three pieces cooperate:

  • The event — a named definition (name is a lowercase slug like user.signed_up, immutable once created; display_name and description are for humans). Names starting with stripe. or shopify. are reserved for provider event sources.
  • The listening automation — any flow whose trigger is Custom event bound to the event's uid, optionally gated by payload_filters (equals / exists rows over dot-notation paths; all rows must match).
  • The fire — one POST /triggers/{uid}/fire call. It resolves the contact, records an audit row (visible in-app under Automations → Trigger events, retained 90 days), and enrolls every matching automation. The payload is snapshotted onto each run, so emails render {{ event.* }} tags from enrollment-time data even if you fire the event again later.

Endpoints

MethodEndpointDescription
GET/triggersPaginated index, newest first, with lifetime fire counts.
POST/triggersCreate: name (slug, required), display_name, description, auto_create_contact, mail_list_uid. Duplicate names return 409.
GET/triggers/{uid}One event with fire count and last_fired_at.
PATCH/triggers/{uid}Update everything except name (immutable — your code references it).
DELETE/triggers/{uid}Delete the event and its fire log. Automations bound to it are kept but never fire again.
POST/triggers/{uid}/fireFire for a contact: email or subscriber_uid, optional payload object (≤16 KB of JSON). Returns {fire_id, runs_started}.
GET/triggers/{uid}/firesRecent fires, newest first, payloads included (90-day retention).

Firing an event

# Define the event once
curl -X POST https://emailflow.ai/api/v1/triggers \
  -H "Authorization: Bearer efa_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "order.completed", "display_name": "Order completed",
        "auto_create_contact": true, "mail_list_uid": "ab12cd34ef" }'

# Fire it from your application on every checkout
curl -X POST https://emailflow.ai/api/v1/triggers/{uid}/fire \
  -H "Authorization: Bearer efa_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order.completed-alice-1720000000" \
  -d '{
    "email": "alice@example.com",
    "payload": { "order": { "id": "ord_42", "total": 129.9, "currency": "USD" } }
  }'

# Response (201)
{
  "data": {
    "fire_id": "64a91b2c7de31",
    "event": "order.completed",
    "email": "alice@example.com",
    "runs_started": 1,
    "created_at": "2026-07-05T13:30:00+00:00"
  }
}

Contact resolution. A subscriber_uid resolves directly. An email is looked up in the event's target list (or across your lists when no list is configured). With auto_create_contact on and a mail_list_uid configured, unknown emails are created in that list as subscribed contacts — without starting the list's welcome automations; only the fired event's own automations enroll. Fires that resolve no contact are still logged (with runs_started: 0) so you can audit misses.

Idempotency. Enrollment is idempotent per contact and automation — a second fire for the same contact logs a fire but does not double-enroll. For network-level retry safety send an Idempotency-Key header (<event>-<user>-<timestamp> works well).

Payload merge tags

Inside any email of a flow started by a custom event, the fire's payload is addressable with dot notation — missing paths render empty, and the fallback grammar applies:

Subject: Thanks for order {{ event.order.id }}!

Your total was {{ event.order.total }} {{ event.order.currency | USD }}.

Replaying runs

Terminal runs (completed, failed, cancelled) can be re-enrolled from the flow's entry node with the original payload snapshot — {{ event.* }} tags render the same values as the first pass. The new run references the original via replayed_from. Non-terminal runs return 409, as do inactive automations and unsubscribed contacts. Requires the automations scope.

curl -X POST https://emailflow.ai/api/v1/automation-runs/{run_uid}/replay \
  -H "Authorization: Bearer efa_YOUR_KEY"

# Response (201) — a NEW run; the original is untouched
{
  "data": {
    "uid": "64a91b2c7de99",
    "status": "waiting",
    "event_payload": { "order": { "id": "ord_42", "total": 129.9, "currency": "USD" } },
    "replayed_from": "64a91b2c7de31",
    "...": "..."
  }
}

Replay is also available in-app from the automation's Contacts view on any completed or failed run.