Documentation

Triggers reference

Every built-in trigger, its event types, and the payload it injects.

A trigger watches for an event and decides whether to start (or resume) an automation. The TriggerRegistry registers a handler per type. When an event reaches AutomationEventListener, the registry finds matching triggers, each runs shouldTrigger and extractContext, and the runner kicks off the workflow.

This page lists every trigger that ships with AppEngine, the upstream event types it listens to, and the variables it leaves on context.variables for downstream steps.

Built-in triggers

FieldTypeDescription
scheduled_timetime-based

Fires on cron, fixed interval, or one-shot at a future timestamp. Listens to scheduled_time, cron_trigger. Config: schedule.type = cron | interval | once; plus cronExpression, interval + unit, or executeAt.

contact_createdCRM event

Fires shortly after a contact is created (within 5 minutes). Listens to contact.created, contact.imported, contact.registered. Config supports source (website | import | api | manual | form), tags, excludeTags, customFields.

email_openedBroadcast event

Fires on email-tracking open events. Listens to email.opened, email.open, email_tracking.open. Payload includes the emailEvent and the contact who opened.

email_clickedBroadcast event

Fires on link-click events. Listens to email.clicked, email.link_clicked, email_tracking.click. Use to detect strong intent on a campaign.

appointment_bookedCalendar event

Listens to appointment.booked, calendar.appointment_created, booking.created. Payload includes the appointment record (id, time, attendees).

page_visitedTracking event

Listens to page.visited, website.page_view, tracking.page_visit. Payload includes the pageVisit (url, timestamp, referrer) and the resolved contact if known.

contact_taggedCRM event

Listens to contact.tagged, contact.tag_added, tag.assigned. Payload includes tagEvent.contactId and tagEvent.tag.

database_changeRepository event

Generic CDC trigger. Listens to data_created, data_updated, data_deleted, social_activity_created, form_submitted, activity_created, message_received, phone_call. Filter by datatype and operation (created | updated | deleted | *).

call_incomingPhone event

Inbound voice call. Used to drive IVR flows.

call_missedPhone event

Missed call. Pair with make_call or send_notification for callback automations.

sms_receivedPhone event

Inbound SMS. Pair with send_notification (SMS) for keyword auto-replies.

Payload shapes

What a step sees on context.variables depends on the trigger.

scheduled_time

{
  "triggerType": "scheduled_time",
  "scheduledAt": "2026-04-25T09:00:00Z",
  "currentTime": "2026-04-25T09:00:01Z",
  "scheduleId": "sched_abc",
  "cronExpression": "0 9 * * MON"
}

contact_created

{
  "contact": {
    "id": "c-123",
    "email": "[email protected]",
    "firstName": "Alice",
    "tags": ["newsletter"],
    "source": "website",
    "createdAt": "2026-04-25T..."
  },
  "triggerResult": {
    "triggerType": "contact_created",
    "contactId": "c-123",
    "contactEmail": "[email protected]",
    "contactName": "Alice",
    "source": "website"
  }
}

email_opened / email_clicked

{
  "emailEvent": {
    "id": "evt-1",
    "messageId": "msg-1",
    "eventType": "opened",
    "timestamp": "2026-04-25T...",
    "url": "https://...",
    "userAgent": "..."
  },
  "contact": { "id": "c-123", "email": "..." }
}

email_clicked adds emailEvent.url (the clicked link).

appointment_booked

{
  "appointment": {
    "id": "appt-1",
    "startsAt": "2026-04-30T15:00:00Z",
    "endsAt": "2026-04-30T15:30:00Z",
    "attendees": ["[email protected]"],
    "bookedAt": "2026-04-25T..."
  },
  "contact": { "id": "c-123" }
}

page_visited

{
  "pageVisit": {
    "url": "https://example.com/pricing",
    "referrer": "https://google.com",
    "visitedAt": "2026-04-25T..."
  },
  "contact": { "id": "c-123" }
}

contact_tagged

{
  "tagEvent": {
    "contactId": "c-123",
    "tag": "vip",
    "taggedAt": "2026-04-25T..."
  }
}

database_change

The most flexible — fires on every data_* repository event. Filter narrowly to avoid runaway evaluations.

{
  "datatype": "lead",
  "operation": "updated",
  "record": { /* the new BaseModel */ },
  "previous": { /* the prior version, on update */ },
  "triggerConfig": { "datatype": "lead", "operation": "updated" }
}
Filter database_change

Without a datatype filter, the trigger evaluates on every write across the org. Always set config.datatype to the collection you care about and pin config.operation if possible.

Recency window

Most contact, email, page-visit, and appointment triggers reject events older than five minutes. This protects against late-arriving webhooks accidentally re-firing automations on stale data. If you replay events deliberately (e.g. backfilling), use POST /automation/:automationId/execute with the variables you want instead.

Circular prevention

database_change runs each event through TriggerGuardService first. By default the guard blocks:

  • An automation triggering itself (preventSelfTrigger).
  • More than 5 nested executions (maxDepth).
  • A re-run against the same record within 1 second (cooldownMs).

Override per-flow via settings.circularPrevention. See flow builder.