A **trigger** decides who enters a workflow and when. Every workflow has exactly one trigger, declared before the steps. An optional `where` filter narrows enrollment to contacts that also match a condition.

## Trigger types

| Trigger | Enrolls a contact when… | Example |
|---|---|---|
| **Segment** | they enter a [Segment](/audience/segments) | `enter on segment "trial-started"` |
| **Event** | a SES engagement **event** occurs for them | `enter on event click` |
| **Activity** | a custom **activity** you send to SendOps is recorded | `enter on activity.purchase` |
| **Date** | a moment relative to a datetime attribute arrives | `enter on 3 days before attr.renewal_date` |


  An **event** is a SES message-engagement signal that SendOps already tracks — `open`, `click`, `delivery`, `bounce`, and the like. An **activity** is a custom behavioral signal *you* send to SendOps (for example `purchase`, `login`, `ticket_opened`), referenced as `activity.<name>`. They live in separate namespaces so the two never collide. See [Activities](/audience/activities) for the full guide — recording them, naming rules, and making their properties queryable.


Add a `where` filter to any trigger to gate enrollment — on an activity trigger, or equally on a Segment one:

```sendflow
workflow "Pro purchasers" v1 {
  enter on activity.purchase where attr.plan = "pro"
  // …steps…
}
```

```sendflow
workflow "US high-value" v1 {
  enter on segment "high-value" where attr.country = "US"
  // …steps…
}
```

The `where` filter is a normal [Segment rule](/audience/segment-syntax) evaluated **against the contact** at the moment of enrollment — it doesn't receive the triggering event's or activity's payload directly. To gate on the triggering activity's own properties, use an activity term with a tight window (the property must be [promoted](/audience/activities#promoted-properties)):

```sendflow
workflow "Big spenders" v1 {
  enter on activity.purchase where exists(activity.purchase where amount > 100 within 1h)
  // …steps…
}
```

## Enrollment scope

*Who joins when you activate.* A trigger decides who enrolls *going forward*. But a workflow doesn't exist until you build and **activate** it — and at that moment there may already be contacts who match the trigger (people already in the Segment, contacts whose `renewal_date` is next week). The `enroll` setting decides whether those existing matches are pulled in too:

| Setting | On activation… |
|---|---|
| `enroll forward` | Enroll **only** contacts who match from activation onward. Existing matches are left alone. |
| `enroll existing` | Also **back-enroll** contacts who already match — a one-time backfill that runs once when you activate. |
| `enroll existing since <duration>` | Back-enroll only matches from the last `<duration>` (e.g. `since 30 days`) — bound how far back the one-time backfill reaches. |

```sendflow
workflow "Renewal reminder" v1 {
  enter on 3 days before attr.renewal_date
  enroll existing since 30 days
  // …steps…
}
```


  Leave `enroll` off and each trigger keeps its natural default: a **Segment** trigger back-enrolls existing members (matching how Segments have always worked), while **event**, **activity**, and **date-relative** triggers are **forward-only** (there's no sensible "replay every past click"). Forward-only enrollment keys on when the event actually **occurred**, not when SendOps received it — so a late or backfilled historical event, one whose timestamp predates activation, won't enroll a forward-only workflow. Set `enroll` explicitly whenever you want the other behavior — most often `enroll existing since <duration>` on a date-relative trigger to catch renewals already in the near future. A date-relative `enroll existing` **must** carry a `since` window; an unbounded look-back would enroll every contact with any past anchor date.


The one-time backfill respects the same [re-entry](#re-entry) and [guardrail](#guardrails) rules as normal enrollment, so it never double-enrolls a contact that normal forward enrollment already caught. In the dashboard, `enroll` is a control in the **Flow settings** panel, and the editor previews roughly how many existing contacts a backfill would enroll — and asks you to confirm — before you activate. When you leave `enroll` undeclared, the Flow settings card shows the **effective** enrollment mode it resolves to from your trigger — a **Forward only** or **Backfill** badge — so you can see the behavior at a glance without reading it out of the trigger type.

## Re-entry

By default a contact goes through a workflow **once, ever** (`reentry once`). To let a contact re-enroll, opt in with either spelling of the same relaxed mode — the two select the **same** behavior and differ only in which trigger type each reads naturally against:

- `reentry on rematch` — reads naturally for a **Segment** trigger: a contact may re-enroll after leaving the trigger Segment and matching it again.
- `reentry per occurrence` — reads naturally for an **event** or **activity** trigger: a contact may re-enroll on a later occurrence.

Whichever you write, re-enrollment becomes possible again once the contact is eligible — subject to the [guardrails](#guardrails) below, which still apply.


  On an `enter on activity.<name>` trigger, `reentry once` (the default) is durable and permanent — it's a Postgres record of "has this contact ever run this workflow," checked every time. It's what actually stops a duplicate activity from producing a duplicate send, regardless of *why* the activity duplicated: a retried request, a dedup check that failed open during a Redis outage, or a periodic sync re-emitting the same milestone. Activity-level dedup (`idempotency_key` / `dedup_mode`, see [Activities](/audience/activities#exactly-once--re-triggering)) reduces how often a duplicate activity happens at all, but `reentry once` is the guarantee that actually matters for "did this contact get emailed twice." Worst case with `reentry once`: a duplicate *timeline row*, never a duplicate *send*.


## Guardrails

Two rules always apply, regardless of re-entry mode:

- **One live journey per contact per workflow.** A contact already moving through a workflow won't start a second concurrent journey through the same one.
- **Converts aren't re-enrolled.** A contact who left through a **named exit** (your success condition) is not re-enrolled while they still match the trigger — so a customer who already converted doesn't get pulled back into the nudge sequence.

See the [SendFlow reference](/workflows/flow-reference) for the exact trigger syntax, and [Sends, consent & approval](/workflows/sends-and-approval) for what happens once a contact is enrolled.