Workflow is Lumail's graph-based automation engine. Use it when a sequence needs branching, event waits, weighted splits, conversion goals, global exits, mutually exclusive groups, immutable published versions, dry runs, or a detailed execution timeline.

## Use this guide

| If you want to...                        | Read                                                           |
| ---------------------------------------- | -------------------------------------------------------------- |
| Build your first graph in the editor     | [Getting started](/docs/workflows/workflow-getting-started)    |
| Choose enrollment events and filters     | [Triggers](/docs/workflows/workflow-triggers)                  |
| Enroll a filtered list by hand           | [Manual enrollment](/docs/workflows/workflow-manual-enrollment) |
| Add conditions, event waits, or loops    | [Branching](/docs/workflows/workflow-branching)                |
| Compare two or more variants             | [A/B testing](/docs/workflows/workflow-ab-testing)             |
| Define and measure conversion            | [Goals and success goals](/docs/workflows/workflow-goals)      |
| Stop runs when subscriber state changes  | [Exit rules](/docs/workflows/workflow-exit-rules)              |
| Understand drafts and immutable versions | [Publishing and versions](/docs/workflows/workflow-publishing) |
| Test safely and inspect results          | [Test runs and timelines](/docs/workflows/workflow-testing)    |

![Workflow editor showing a complete graph and its configuration rails](/docs/workflows/editor-overview.png)

## Mental model

Every Workflow has four related layers:

| Layer             | Purpose                                                                                                                     |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------- |
| Workflow          | Organization-owned name, status, repeat settings, group assignment, exits, and current draft                                |
| Draft definition  | Editable complete `{ steps, edges }` graph; changing it does not change existing runs                                       |
| Published version | Immutable snapshot created from the draft; every publish increments the version                                             |
| Subscriber run    | One subscriber executing one published version with its own status, current step, schedule, context, metadata, and timeline |

Existing runs remain pinned to the version they started with. Editing or publishing a new draft never rewrites an existing run.

## Workflow and run statuses

Workflow statuses:

| Status     | Meaning                                                             |
| ---------- | ------------------------------------------------------------------- |
| `DRAFT`    | Never published; editable but cannot enroll subscribers             |
| `ACTIVE`   | Published and eligible for new enrollment and execution             |
| `PAUSED`   | Published but new execution is paused until reactivated             |
| `ARCHIVED` | Removed from normal operation while preserving versions and history |

Subscriber run statuses:

| Status      | Meaning                                                                      |
| ----------- | ---------------------------------------------------------------------------- |
| `ACTIVE`    | Executing or ready to execute a step                                         |
| `WAITING`   | Waiting for time, an event, audience changes, or sending access              |
| `COMPLETED` | Reached the end, a goal, or an explicit exit                                 |
| `CANCELED`  | Stopped by an exit rule, group conflict, manual removal, or lifecycle action |
| `FAILED`    | Stopped by an unrecoverable execution failure                                |

Run data includes `workflowVersionId`, `currentStepId`, `nextActionAt`, `runNumber`, `isDryRun`, `waitingForAudienceChange`, `context`, `metadata`, lifecycle timestamps, and `goalReachedId`. Timeline events record step starts/completions, decisions, waits, dry-run skips, exits, goals, and failures.

## Graph contract

The definition is a complete graph:

```json
{
  "steps": [],
  "edges": [],
  "exitMode": "DISABLED",
  "exitRules": [],
  "successGoals": []
}
```

Validation requires:

- Unique stable step and edge IDs
- At least one `TRIGGER`
- Every non-trigger step reachable from a trigger
- Every edge source and target to reference an existing step
- `CONDITION` steps to have `YES` and `NO` edges
- `WAIT_UNTIL` steps to have `MATCHED` and `TIMEOUT` edges
- `SPLIT` percentages to total 100
- `MOVE_TO_STEP` to target an existing different step
- `GOAL` steps to be terminal with no outgoing edge
- Empty exit rules to remain inactive until they contain a subscriber filter

The supported edge handles are `DEFAULT`, `YES`, `NO`, `MATCHED`, `TIMEOUT`, and `SPLIT_n` such as `SPLIT_1`.

## Trigger events

A trigger can listen to one `eventName` or multiple `eventNames`, plus subscriber audience filters and event-specific filters.

Supported events:

- `subscriber.created`
- `subscriber.updated`
- `subscriber.unsubscribed`
- `subscriber.tag.added`
- `subscriber.tag.removed`
- `subscriber.field.changed`
- `subscriber.email.sent`
- `subscriber.email.delivered`
- `subscriber.email.opened`
- `subscriber.email.clicked`
- `subscriber.email.bounced`
- `subscriber.payment`
- `subscriber.refund`
- `subscriber.capture_page.submitted`
- `subscriber.workflow.completed`
- `subscriber.workflow.goal_reached`

Event-filter keys are strict:

| Key          | Type               | Valid events                                        |
| ------------ | ------------------ | --------------------------------------------------- |
| `tagIds`     | Array of tag IDs   | Tag added and tag removed                           |
| `product`    | Product identifier | Payment and refund                                  |
| `campaignId` | Campaign ID        | Email sent, delivered, opened, clicked, and bounced |

Unknown keys are rejected. An omitted `eventFilters` object matches every event of the selected type.

## Step types

### `TRIGGER`

Starts enrollment from one or more subscriber events. Audience filters must match when the event is evaluated.

```json
{
  "id": "trigger",
  "type": "TRIGGER",
  "config": {
    "eventName": "subscriber.tag.added",
    "eventFilters": { "tagIds": ["tag_123"] },
    "filters": []
  }
}
```

### `EMAIL`

Sends the internal workflow email owned by that exact workflow step. The stored graph references its `campaignId`, but agent-built drafts should provide complete inline email content to `configure_workflow_draft`.

Workflow email rules:

- Lumail creates a dedicated campaign with `type: WORKFLOW`.
- One internal campaign belongs to exactly one workflow and step.
- A broadcast or another step's campaign is duplicated, never linked directly.
- Internal workflow campaigns do not appear in normal broadcast campaign lists.
- The run fetches current subscriber data before rendering.
- Sending uses the same durable bulk queue and organization quota as campaigns.
- Sending suspension parks the run instead of discarding it.

Never call `create_campaign` to create a Workflow email.

### `WAIT`

Pauses execution using one of three configurations:

- `DURATION`: positive duration in `minutes`, `hours`, or `days`
- `TIME`: next occurrence of a local `HH:mm` time
- `DAY`: next named weekday, optionally combined with time in the editor

Organization timezone controls calendar waits. For J0/J1/J3/J5 schedules, use incremental waits of 1, 2, and 2 days.

### `WAIT_UNTIL`

Waits for either audience filters (`mode: FILTER`) or a subscriber event (`mode: EVENT`). It can include a timeout duration and must route through both `MATCHED` and `TIMEOUT` edges.

Event mode uses the same strict event-filter keys as triggers. Filter mode wakes when subscriber data changes and the filters become true.

### `CONDITION`

Evaluates audience filters once and routes to `YES` or `NO`.

### `SPLIT`

Assigns subscribers to weighted branches. Each branch has a handle, percentage, and optional label; percentages must total exactly 100.

### `MOVE_TO_STEP`

Moves execution directly to another existing step ID. It cannot target itself or a missing step.

### `ACTION`

Supported action types:

| Action            | Data                                         | Behavior                                     |
| ----------------- | -------------------------------------------- | -------------------------------------------- |
| `ADD_TAGS`        | `tagIds`                                     | Adds tags and may trigger other workflows    |
| `REMOVE_TAGS`     | `tagIds`                                     | Removes tags                                 |
| `SET_FIELD`       | `fieldId`, `value`                           | Updates a subscriber field                   |
| `ADD_TO_WORKFLOW` | `workflowId`, optional `workflowStepId`      | Enrolls in an eligible workflow              |
| `UNSUBSCRIBE`     | None                                         | Unsubscribes the subscriber                  |
| `WEBHOOK`         | `url`, optional `secret`, optional `payload` | POSTs workflow context; `payload` is nested, never merged |

A terminal `ADD_TAGS` handoff is deferred until the emitting run completes, allowing First Wins groups to chain workflows safely. Non-terminal tags are evaluated immediately.

Webhook secrets are redacted from agent reads as `[REDACTED]`. That placeholder copies a **previously stored real** secret on a later graph update. First configure, or a new webhook step, must write the real secret or omit it — sending `[REDACTED]` is rejected when no real secret is stored. See [Webhook Step](/docs/workflows/workflow-webhook-step).

### `GOAL`

Marks a reusable workflow goal and completes the run. It must not have an outgoing edge. Agent configuration accepts an existing `goalId` or an inline `createGoal` object; goal creation is case-insensitively idempotent inside the same transaction.

### `EXIT`

Terminates one routed branch as exited and can store an optional machine-readable reason. This is different from a global exit rule that watches every run before configured step boundaries.

## Success goals

Top-level `successGoals` watch subscriber filters and optional events while a run is `ACTIVE` or `WAITING`.

Each success goal contains:

- Stable `id` and display `name`
- `goalId` or inline `createGoal`
- Audience `filters` (subscriber state)
- Optional `eventName` and strict `eventFilters` (which event)
- `completeWorkflow`

`eventName` + `eventFilters` select the event. `filters` is subscriber state. They are not interchangeable. Empty `filters` is fine only when a tag-added / tag-removed event also pins `eventFilters.tagIds`. Do not copy the trigger advice “don’t repeat the tag as an audience filter” onto success goals.

If the conversion is that the subscriber now has a tag, prefer `eventName` omitted plus a TAG `BELONGS_TO_ANY` filter. If you watch `subscriber.tag.added` itself, pin `eventFilters.tagIds`. Omitted or empty `tagIds` matches every tag mutation; `completeWorkflow: true` then completes every active run.

Filters inside one success goal use AND semantics. Goals are evaluated in array order and the first match is recorded. With `completeWorkflow: false`, Lumail records the conversion and keeps the run active. With `true`, it records the goal and completes the run.

## Global exits

`exitMode` controls when `exitRules` are evaluated:

| Mode               | Evaluation point              |
| ------------------ | ----------------------------- |
| `DISABLED`         | Never                         |
| `BEFORE_EMAIL`     | Immediately before each email |
| `BEFORE_EACH_STEP` | Before every step             |

Filters inside one rule use AND semantics; multiple rules use OR semantics. A match cancels the run before the step executes. An enabled mode with no configured filters is valid but inactive: it never exits a subscriber until at least one rule contains a filter.

## Repeat settings

`allowRepeats` controls whether the same subscriber may start the workflow again. `repeatCooldownMinutes` sets the minimum delay between runs and defaults to 1,440 minutes. Active-run deduplication still prevents simultaneous duplicate runs in the same workflow.

## Workflow groups

Groups make workflows mutually exclusive for a subscriber. Conflicts are matched by organization, subscriber, and group membership across active or waiting runs.

| Strategy          | Label                 | Result                                                        |
| ----------------- | --------------------- | ------------------------------------------------------------- |
| `CANCEL_EXISTING` | Latest wins           | Cancel existing conflicting runs, then start the new workflow |
| `SKIP_NEW`        | First wins            | Keep the existing run and skip the incoming enrollment        |
| `RUN_FIRST_STEP`  | Send first email only | Skip the incoming run but send its first email once           |

Group assignment affects future enrollments. Detaching a workflow or deleting its group does not alter or cancel existing runs. See [Workflow Groups](/docs/workflows/workflow-groups) for chaining and agent tools.

## Drafts, publishing, and status changes

Saving the draft changes only editable state. Publishing validates the complete graph and snapshots it as a new immutable version.

- First publish normally activates a `DRAFT` workflow unless `activate: false` is explicit.
- Publishing an already active workflow never deactivates it.
- Publishing a paused workflow never silently reactivates it.
- Activation, pausing, archiving, and publishing are separate lifecycle decisions.
- Existing runs continue on their enrolled version.

Publishing and status changes are confirmation-gated through MCP and the Tools API because activation may send emails or mutate subscribers.

## Configure a complete draft with an agent

The safe sequence is:

1. Call `get_skill` with `{ "type": "workflow" }`.
2. Call `get_workflow` and preserve its exact `updatedAt`.
3. Discover referenced tags, fields, senders, snippets, goals, workflows, and groups with read-only tools.
4. Call `configure_workflow_draft` once with complete `steps` and `edges` arrays.
5. Read the returned definition and `configuredEmails` mapping.
6. Read and render every internal email.
7. Publish separately only after explicit human authorization.

`configure_workflow_draft` is atomic and never publishes, activates, enrolls, or sends. A timeout or rollback leaves the draft unchanged. Retry the same complete payload only after a fresh read and updated concurrency value.

```json
{
  "workflowId": "workflow-id",
  "expectedUpdatedAt": "2026-07-27T10:00:00.000Z",
  "settings": {
    "name": "Welcome journey",
    "allowRepeats": false,
    "groupId": "group-id",
    "exitMode": "BEFORE_EACH_STEP",
    "exitRules": [
      {
        "id": "exit-unsubscribed",
        "name": "Customer tag present",
        "filters": [
          {
            "type": "TAG",
            "field": "tags",
            "operator": "BELONGS_TO_ANY",
            "tagIds": ["tag-customer"]
          }
        ]
      }
    ]
  },
  "steps": [
    {
      "id": "trigger",
      "type": "TRIGGER",
      "config": { "eventName": "subscriber.created", "filters": [] }
    },
    {
      "id": "email-welcome",
      "type": "EMAIL",
      "name": "Welcome",
      "email": {
        "subject": "Welcome aboard",
        "preview": "Your first steps",
        "content": {
          "type": "doc",
          "content": [
            {
              "type": "paragraph",
              "content": [{ "type": "text", "text": "Welcome!" }]
            }
          ]
        }
      }
    }
  ],
  "edges": [
    {
      "id": "edge-trigger-email",
      "source": "trigger",
      "target": "email-welcome",
      "sourceHandle": "DEFAULT"
    }
  ]
}
```

Use stable step and edge IDs. Stable IDs let retries update the same internal email instead of creating replacements.

## Workflow tools

Organizations receive these tools through the in-app assistant, MCP, SDK, CLI, and Tools API:

| Tool                              | Purpose                                                                         |
| --------------------------------- | ------------------------------------------------------------------------------- |
| `list_workflows`                  | List workflows with status, group, publication, and active-run data             |
| `get_workflow`                    | Read the complete draft, group, settings, goals, version, and concurrency value |
| `create_workflow`                 | Create an empty draft                                                           |
| `configure_workflow_draft`        | Atomically configure graph, emails, goals, and settings                         |
| `update_workflow_draft`           | Replace an existing complete graph                                              |
| `publish_workflow`                | Publish an immutable version; confirmation-gated                                |
| `update_workflow_status`          | Activate, pause, or archive; confirmation-gated                                 |
| `add_subscriber_to_workflow`      | Manually enroll one eligible subscriber                                         |
| `add_subscribers_to_workflow`     | Manually enroll a filtered set (Select all). Use `dryRun: true` first           |
| `remove_subscriber_from_workflow` | Cancel active runs for a subscriber                                             |
| `get_subscriber_workflow_runs`     | Inspect a subscriber's runs: status, current step, `nextActionAt`               |
| `fast_forward_workflow_subscriber` | Expire the current WAIT / WAIT_UNTIL and resume now. One wait per call          |
| `delete_workflow`                 | Delete an unused draft or archive history; confirmation-gated                   |
| `list_workflow_groups`            | Discover groups, strategies, IDs, and workflow counts                           |
| `get_workflow_group`              | Inspect workflows, active runs, strategy, and concurrency                       |
| `create_workflow_group`           | Idempotently create a group                                                     |
| `update_workflow_group`           | Rename or change strategy with optimistic concurrency                           |
| `set_workflow_group`              | Assign or detach a workflow without replacing its graph                         |
| `delete_workflow_group`           | Safely delete and optionally detach assigned workflows; confirmation-gated      |

The live schema is authoritative:

```bash
lumail tools get configure_workflow_draft --account <org>
lumail tools get create_workflow_group --account <org>
lumail tools get set_workflow_group --account <org>
```

## Dry runs, timelines, and observability

The editor can start an isolated dry run for a subscriber. Dry runs evaluate routing and record timeline events while skipping real email and action side effects.

Use the workflow dashboard and run timeline to inspect:

- Enrollment and run status
- Current step and next action time
- Branch decisions and wait reasons
- Email/action dry-run skips
- Goal and exit events
- Failures and stuck runs
- Active counts by step

Dry runs are excluded from production active-run metrics.

## Verification checklist

Before claiming a Workflow change is complete:

1. Read `get_workflow` again and compare `updatedAt`, steps, edges, settings, goals, group, and `publishedVersionId`.
2. Read every `configuredEmails` campaign and compare sender, subject, preview, and TipTap content.
3. Render every internal email and require non-empty HTML/text plus an unsubscribe link.
4. Confirm internal `WORKFLOW` campaigns do not appear as broadcasts.
5. Reconstruct incremental wait timing and every branch path.
6. Read group data again after group changes and verify conflict scope.
7. For draft-only work, prove version and publication timestamps did not change.
8. For published work, verify the intended version and status separately.
9. Inspect run timelines and server logs for runtime errors.

## Related documentation

- [Getting started](/docs/workflows/workflow-getting-started)
- [Triggers](/docs/workflows/workflow-triggers)
- [Manual enrollment](/docs/workflows/workflow-manual-enrollment)
- [Branching](/docs/workflows/workflow-branching)
- [A/B testing](/docs/workflows/workflow-ab-testing)
- [Goals and success goals](/docs/workflows/workflow-goals)
- [Exit rules](/docs/workflows/workflow-exit-rules)
- [Publishing and versions](/docs/workflows/workflow-publishing)
- [Test runs and timelines](/docs/workflows/workflow-testing)
- [Workflow Groups](/docs/workflows/workflow-groups)
- [Email Step](/docs/workflows/workflow-email-step)
- [Wait Step](/docs/workflows/workflow-wait-step)
- [Action Step](/docs/workflows/workflow-action-step)
- [Webhook Step](/docs/workflows/workflow-webhook-step)
- [MCP](/docs/api-reference/mcp)
- [CLI](/docs/api-reference/cli)
- [Tools API](/docs/ai-integration/tools-api)
