# Settings

> Triggers, named exits, send windows, re-entry, frequency caps and enrolment scope.

Source: https://www.sendlang.com/docs/sendflow/settings
Section: SendFlow

Settings describe the shape of a campaign — who comes in, who goes out, and the
rules that apply throughout. They all sit at the top of the workflow, **before**
the first statement.

```sendflow renewal.flow
workflow "Renewal" v1 {
  enter on 3d before attr.renewal_date where attr.plan = "pro"
  exit "renewed" when exists(activity.invoice_paid within 7d)
  send window 9am-6:30pm in contact timezone
  reentry per occurrence
  frequency cap 4 per 7d
  enroll existing since 30d

  send "renewal-notice" via topic "billing"
}
```

Putting a setting after a statement is a **parse error**, not a warning. Their
order among themselves does not matter, and the formatter preserves whatever
order you chose.

## enter — the trigger

Exactly one, always. A workflow with no `enter` can never start, and one with two
has no defined beginning; both are errors.

There are four kinds of trigger.

**A segment.** The contact enters when they match a saved SendQL segment.

**Syntax form, not a complete program:**

```sendflow
enter on segment "trial-started"
```

**An activity.** The contact enters when they do something in your product. This
is the natural trigger for most lifecycle campaigns.

**Syntax form, not a complete program:**

```sendflow
enter on activity.cart_updated
```

**An event.** The contact enters on an email event — one of the eight built-ins.
Legal, but usually a deliverability alarm rather than a campaign.

**Syntax form, not a complete program:**

```sendflow
enter on event bounce
```

**A date, relative to an attribute.** The contact enters a fixed duration before
or after one of their own datetime attributes.

**Syntax form, not a complete program:**

```sendflow
enter on 3d before attr.renewal_date
enter on 1d after attr.trial_ends_at
```

This is the only way to act on a date in the *future*, and it is the reason
SendQL has no need to express one. Each contact is enrolled relative to *their*
date, not a global one.

**Any trigger can take a `where` filter**, which is a full SendQL predicate:

**Syntax form, not a complete program:**

```sendflow
enter on activity.signup where attr.plan = "free" and not suppressed
```

## exit — named exits

Any number, including none. An exit is a **goal**: a predicate that is checked
before every single step, and the moment it is true, the contact leaves.

**Syntax form, not a complete program:**

```sendflow
exit "converted" when attr.plan != "trial"
exit "purchased" when exists(activity.order)
```

The name is not decoration — it is what conversion reporting counts. Two exits
may not share a name.

An exit fires wherever the contact happens to be: mid-`wait`, inside a `repeat`,
between two sends. This is what makes them worth using over a defensive `if`
before each send. Declare the goal once.

## send window

At most one. Restricts sends to a window in **the contact's own timezone**, so
nobody gets a 3am marketing email.

**Syntax form, not a complete program:**

```sendflow
send window 9am-6:30pm in contact timezone
```

Times are 12-hour with an `am`/`pm` suffix; minutes are optional (`9am` and
`9:00am` are the same thing, and the formatter will normalise the second to the
first). A window that opens and closes at the same time is a warning — it is
almost certainly a typo for "all day".

## reentry

At most one. What happens when a contact who has already been through the
workflow qualifies again.

| Form | Meaning |
|---|---|
| `reentry once` | Never re-enter. **The default** when undeclared. |
| `reentry on rematch` | Re-enter if they stop matching and then match again. |
| `reentry per occurrence` | Re-enter every time the trigger fires. |

`per occurrence` is what you want for a cart-abandonment flow — a contact can
abandon many carts. `once` is what you want for onboarding.

## frequency cap

At most one. Overrides your organisation-wide sending cap **for this workflow**.

**Syntax form, not a complete program:**

```sendflow
frequency cap 4 per 7d
frequency cap off
```

Two subtleties worth getting right:

- **Counting stays cross-workflow.** The cap changes this workflow's threshold and
  window, not what gets counted against it. Messages from your other campaigns
  still fill the contact's quota.
- **`off` exempts the workflow entirely**, which is what a time-critical
  transactional stream needs. Use it sparingly and deliberately.

Undeclared, your organisation's default applies.

## enroll — enrolment scope

At most one. Whether contacts who *already* match are back-enrolled when the
workflow is switched on.

| Form | Meaning |
|---|---|
| `enroll forward` | Only contacts who match from activation onward. **The default.** |
| `enroll existing` | Also back-enrol everyone who already matches — a one-time backfill. |
| `enroll existing since 30d` | Back-enrol, but only as far back as the window. |

**The default is forward-only, for every trigger kind**, and it is worth pausing
on. Switching on `enter on segment "all-users"` does *not* mail every current
member of that segment — it enrols only the contacts who match from that moment
onward. Back-enrolling your history is opt-in, precisely because the alternative
is a campaign that sends to your entire list the second you turn it on.

If you *do* want the backfill, bound it:

**Syntax form, not a complete program:**

```sendflow
enroll existing since 30d
```

`enroll existing` with **no** `since` on a date-relative trigger is an error,
because an unbounded backfill there would enrol every contact who has ever had an
anchor date — which is all of them, forever.

## The full set

| Setting | Cardinality | Default when undeclared |
|---|---|---|
| `enter on …` | exactly one | — (required) |
| `exit "<name>" when …` | any number | no goals |
| `send window …` | at most one | send at any hour |
| `reentry …` | at most one | `once` |
| `frequency cap …` | at most one | your org-wide cap |
| `enroll …` | at most one | `forward` |
