Workflow
Complete guide to Workflow graphs, versions, runs, groups, goals, exits, agent tools, publishing, and verification.
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 |
| Choose enrollment events and filters | Triggers |
| Enroll a filtered list by hand | Manual enrollment |
| Add conditions, event waits, or loops | Branching |
| Compare two or more variants | A/B testing |
| Define and measure conversion | Goals and success goals |
| Stop runs when subscriber state changes | Exit rules |
| Understand drafts and immutable versions | Publishing and versions |
| Test safely and inspect results | Test runs and timelines |

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:
{
"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
CONDITIONsteps to haveYESandNOedgesWAIT_UNTILsteps to haveMATCHEDandTIMEOUTedgesSPLITpercentages to total 100MOVE_TO_STEPto target an existing different stepGOALsteps 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.createdsubscriber.updatedsubscriber.unsubscribedsubscriber.tag.addedsubscriber.tag.removedsubscriber.field.changedsubscriber.email.sentsubscriber.email.deliveredsubscriber.email.openedsubscriber.email.clickedsubscriber.email.bouncedsubscriber.paymentsubscriber.refundsubscriber.capture_page.submittedsubscriber.workflow.completedsubscriber.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.
{
"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 inminutes,hours, ordaysTIME: next occurrence of a localHH:mmtimeDAY: 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.
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
idand displayname goalIdor inlinecreateGoal- Audience
filters(subscriber state) - Optional
eventNameand stricteventFilters(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 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
DRAFTworkflow unlessactivate: falseis 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:
- Call
get_skillwith{ "type": "workflow" }. - Call
get_workflowand preserve its exactupdatedAt. - Discover referenced tags, fields, senders, snippets, goals, workflows, and groups with read-only tools.
- Call
configure_workflow_draftonce with completestepsandedgesarrays. - Read the returned definition and
configuredEmailsmapping. - Read and render every internal email.
- 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.
{
"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:
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:
- Read
get_workflowagain and compareupdatedAt, steps, edges, settings, goals, group, andpublishedVersionId. - Read every
configuredEmailscampaign and compare sender, subject, preview, and TipTap content. - Render every internal email and require non-empty HTML/text plus an unsubscribe link.
- Confirm internal
WORKFLOWcampaigns do not appear as broadcasts. - Reconstruct incremental wait timing and every branch path.
- Read group data again after group changes and verify conflict scope.
- For draft-only work, prove version and publication timestamps did not change.
- For published work, verify the intended version and status separately.
- Inspect run timelines and server logs for runtime errors.