> For the complete documentation index, see [llms.txt](https://flapjax.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://flapjax.gitbook.io/docs/automations/example-automations.md).

# Example Automations

{% hint style="info" %}
Three worked recipes, end to end, whose shape you can copy. Each names the exact actions it uses, walks the build node by node, and explains *why* it fits together that way. For the building blocks, see [Actions](/docs/automations/actions.md) and the [Flow Builder guide](/docs/automations/flow-builder.md).
{% endhint %}

These recipes use generic products, orders and accounts, so the mechanics stand on their own. They lean on two ideas worth reading first.

* [Sequences vs. workflows: which to use](/docs/automations/sequences-vs-workflows.md) covers the person-and-run distinction. It decides recipes 1 and 2, which are sequences, against recipe 3, which is a workflow.
* [How Flapjax processes your events](/docs/start-here/how-your-data-flows.md) covers what happens at runtime, including the rule shaping every journey below: **a run stops after any send or delay, then resumes later.**

Each recipe closes with a **Why it's built this way** note, tying the design back to those mechanics.

***

## Recipe 1: welcome sequence

**Goal:** a new person arrives, so send a welcome email at once, wait a day, then send a follow-up that depends on whether they completed their profile.

This is the canonical **sequence**: one person, a journey over time, delays and a branch. A sequence already knows *who* it works with, so every step reads that person's data with no lookup.

### The build

```mermaid
flowchart TD
    T["Trigger: Person added<br/>re-entry none"] --> E1["Send Email: Welcome<br/>subject and content use {{first_name}}"]
    E1 --> D["Delay Timer: 1 day"]
    D --> B{"If/Else:<br/>profile_completed = true?"}
    B -->|True| E2["Send Email: 'You're all set'"]
    B -->|False| E3["Send Email: 'Finish your profile'"]
```

Node by node:

1. **Trigger: rule-based, on the&#x20;*****Person added*****&#x20;event.** Choose "person added" on the entry node. Narrow it with a segment or custom rules if you want, say only people with an email address. Set the **re-entry policy to&#x20;*****No re-entries allowed*** so each person gets welcomed once.
2. **Send Email, the welcome.** Compose the subject and body. Put the `{{first_name}}` variable in the greeting and give it a **fallback**, say "there", so a person with no first name reads "Hi there," rather than "Hi ,". Variables use the `{{variable.name}}` syntax and insert from the `{ }` menu. You set fallbacks per variable, on the variable chip. See [Actions → Variables & Fallback Values](/docs/automations/actions.md).
3. **Delay Timer, 1 day.** A relative wait. The run pauses here.
4. **If/Else, on profile completion.** One rule group, such as `person.profile_completed equals true`. True and False leave the node on separate paths.
5. **Send Email** on each path: a "you're all set" note on the True path, a "finish your profile" nudge on the False path.

### Actions used

| Step                | Action                      | Reference                                                             |
| ------------------- | --------------------------- | --------------------------------------------------------------------- |
| Trigger             | Enter Sequence (rule-based) | [Triggers & Scheduling](/docs/automations/triggers-and-scheduling.md) |
| Welcome / follow-up | Send Email                  | [Communication actions](/docs/automations/actions/communication.md)   |
| Wait                | Delay Timer                 | [Delays](/docs/automations/actions/delays.md)                         |
| Branch              | If/Else (`branch_if`)       | [Flow control](/docs/automations/actions/flow-control.md)             |

### Why it's built this way

* **The trigger seeds the person for free.** A sequence with a rule-based trigger starts its run with the whole person in context, as `person.*`, plus the triggering `event.*`. That is why Send Email targets "the current person" with no recipient lookup, and why `{{first_name}}` resolves.
* **The send stops the run, then the run resumes.** After the welcome email the run **stops**. It hands the message to the sending service and parks. The journey continues to the delay once delivery succeeds.

  That is deliberate. Everything the run produced before the send is preserved and restored on resume, and a send that never succeeds never advances the journey. Nothing half-sends and corrupts later steps.
* **The delay stops the run too.** The 1-day wait is no sleeping thread. Flapjax serializes the run and its scheduler resumes it later. That is what makes long waits cheap and reliable.
* **The fallback carries weight.** A missing `{{first_name}}` inside enriched email text becomes an empty string with no fallback set, so set one. See [Actions → Variables & Fallback Values](/docs/automations/actions.md).

***

## Recipe 2: re-engagement sequence

**Goal:** send a "we miss you" email, watch whether the person **opens** it, and branch on the answer. A thank-you for an open, a second nudge on another channel for silence.

This recipe branches on **an event that has yet to happen**, through the *Delay Until Event* action. Rather than waiting a fixed span, the run waits for a specific message-status event, here the re-engagement email being **opened**, and resumes down whichever path matches.

### The build

```mermaid
flowchart TD
    T["Trigger: enrolled manually or by rule"] --> E1["Send Email: 'We miss you'"]
    E1 --> W["Delay Until Event:<br/>wait for 'Email opened'<br/>timeout 3 days"]
    W -->|Opened| A["Send Email: 'Thanks for coming back'"]
    W -->|Timed out, not opened| B["Send SMS: second nudge"]
```

Node by node:

1. **Trigger.** Any sequence trigger works. Enrol people manually, from the People page or another automation, or fire on a rule watching a "gone quiet" signal. Set a re-entry policy that suits the campaign.
2. **Send Email, the re-engagement message.** As in recipe 1, the run stops here and resumes on successful delivery.
3. **Delay Until Event, waiting for&#x20;*****Email opened*****.** Configure a **case** that waits for the *Email opened* status event, plus a **timeout**, say 3 days. The node has two exits: a **matched path** for the event arriving, and a **timeout path**, also called the default path, for silence.
   * **Opened.** The run resumes down the matched path.
   * **Not opened.** The timeout elapses and the scheduler resumes the run down the **default path**.
4. **Matched path: Send Email**, a "thanks for coming back".
5. **Timeout path: Send SMS**, a second nudge on another channel.

### Actions used

| Step                  | Action            | Reference                                                             |
| --------------------- | ----------------- | --------------------------------------------------------------------- |
| Trigger               | Enter Sequence    | [Triggers & Scheduling](/docs/automations/triggers-and-scheduling.md) |
| Re-engagement message | Send Email        | [Communication actions](/docs/automations/actions/communication.md)   |
| Wait for open         | Delay Until Event | [Delays](/docs/automations/actions/delays.md)                         |
| Second nudge          | Send SMS          | [Communication actions](/docs/automations/actions/communication.md)   |

### Why it's built this way

* **"Wait until X, otherwise Y" is a single node.** Delay Until Event *is* the branch. The matched path and the timeout path leave the same node, so you never build a poll-and-check loop.

  The event you wait on is a **message-status event**: delivered, opened, clicked or bounced, emitted by the delivery provider's callback. Those same events can trigger a rule-based automation. Here they *advance* a waiting run rather than *start* one.
* **The timeout makes it safe.** With no timeout, a run waiting on an event that never arrives waits forever. The timeout guarantees the journey resolves, since the scheduler forces the run down the default path once the wait expires.
* **Anything captured before the wait gets snapshotted.** Parking on Delay Until Event serializes the run's context. Values referencing earlier action outputs freeze at that moment, since the matcher holds only the callback event once the awaited event arrives. Build your matched and timeout branches on data you already have, rather than data you expect to change during the wait.
* **Delays and waits belong in sequences.** The whole pattern fits a sequence, which follows one person over time. A workflow, as in recipe 3, cannot pause this way inside a loop. Its note covers that.

***

## Recipe 3: data-quality workflow

**Goal:** once a day, find every account record missing a required field, say a country, and act on each one. Flag the record, then hand the related person to a sequence that asks them to fill it in.

This is a **workflow**, not a sequence. A workflow firing is a **run with no person attached**, built for finding and processing *sets* of things. The pattern is the canonical one: schedule, then find and loop, then act per item, referencing each item and its person through a **variable**.

### The build

```mermaid
flowchart TD
    T["Trigger: recurring schedule, daily"] --> F["Find Records:<br/>accounts where country is empty<br/>match summary and count"]
    F --> L["Loop: over accounts where country is empty"]
    L --> U["Update Record: set needs_review = true<br/>per loop item"]
    U --> EN["Enrol in Sequence:<br/>person = loop_iteration variable"]
```

Node by node:

1. **Trigger: recurring schedule, daily.** Pick a frequency, time and timezone on the entry node. Each tick creates a fresh run. Workflows carry no re-entry concept, since every tick is a new run.
2. **Find Records, for accounts missing a country.** Search the accounts stack with a rule such as `country is empty`. Find Records returns a **summary of the matching set**, meaning its count and metrics, which answers "how many need fixing today". It hands you no individual rows to walk. Iteration is the Loop's job.
3. **Loop, over the matching records.** Add a Loop set to iterate **records** in the accounts stack, with the same `country is empty` filter. The Loop fetches its own batch, capped at 10,000, and runs the body once per record. Each record reaches the body under `loop_iteration.*`.
4. **Inside the loop: Update Record.** Set a flag attribute such as `needs_review = true` on the current record. Records actions work in workflows, so this step runs directly.
5. **Inside the loop: Enrol in Sequence.** Enrol the record's **person** into a manual "complete your profile" sequence, naming the person with a **variable** from the loop item, meaning the record's person attribute.

   The workflow can neither email nor update the person itself. It hands them to a sequence, which runs the per-person journey with its own person context.

### Actions used

| Step                | Action                              | Reference                                                             |
| ------------------- | ----------------------------------- | --------------------------------------------------------------------- |
| Trigger             | Enter Workflow (recurring schedule) | [Triggers & Scheduling](/docs/automations/triggers-and-scheduling.md) |
| Count the set       | Find Records                        | [Record actions](/docs/automations/actions/records.md)                |
| Iterate             | Loop                                | [Flow control](/docs/automations/actions/flow-control.md)             |
| Flag each record    | Update Record                       | [Record actions](/docs/automations/actions/records.md)                |
| Hand off the person | Enrol in Sequence                   | [Flow control](/docs/automations/actions/flow-control.md)             |

### Why it's built this way

* **A workflow has no person, so you build context by looping.** Nothing in the trigger says "this person". The run knows it fired on a schedule and nothing more. The Loop puts an item in scope, and inside it you reference the record and its person through `loop_iteration.*` variables. That is the run-centric model described in [Sequences vs. workflows](/docs/automations/sequences-vs-workflows.md).
* **The People action category stays out of workflows.** Update Person, Delete Person, Assign Label, Remove Label and Create Note are **sequence-only**, since a workflow holds no person context to apply them to. That is why this recipe *enrols* the person into a sequence rather than acting on them directly. Enrolment starts a fresh, person-centric run that *can* use those actions.
* **Enrol in Sequence needs the person named here.** Inside a sequence, the action takes "the current person" on its own. Inside a workflow no current person exists, so point it at one with a variable from the loop item. The target must be an **active, manual** sequence.
* **No delays inside a workflow loop.** A data-quality job needing a timed pause takes a delay at the top level of the flow, outside the loop. Delay actions stay out of workflow loops. Per-person delays and branching, as in recipes 1 and 2, belong in the sequence you enrolled into.

***

## Quick recap

* **Recipe 1, a sequence.** The trigger seeds the person, sends and delays stop then resume the journey, and fallbacks keep merge fields clean.
* **Recipe 2, a sequence.** Delay Until Event turns "wait for an open, otherwise nudge" into one branch with a safety-net timeout.
* **Recipe 3, a workflow.** Schedule, then find and loop, then act per item, using variables for the person. A workflow has no person context and cannot run People actions directly.

Related: [Sequences](/docs/automations/sequences.md) · [Workflows](/docs/automations/workflows.md) · [Triggers & Scheduling](/docs/automations/triggers-and-scheduling.md) · [Actions](/docs/automations/actions.md) · [Sequences vs. workflows](/docs/automations/sequences-vs-workflows.md) · [How Flapjax processes your events](/docs/start-here/how-your-data-flows.md)
