Example: Invitation Workflow¶
A simple workflow that sends an email invitation to a supplied address, waits for the recipient to accept or reject, and then sends a follow-up email based on their response.
Overview¶
This workflow uses four steps between Start and End:
- A Notification step that sends the invitation email (with acceptance required)
- A Condition step that checks the recipient's response
- Two Notification steps for the welcome or sorry follow-up emails
Step 1: Create a New Workflow¶
Navigate to a project, then create a new workflow. In the designer, fill in:
- Name: e.g. "Invitation Flow"
- Description: e.g. "Sends an invitation and follows up based on the recipient's response"
- Category:
user - Subject Variable:
user(auto-created) - Error Strategy:
stop - Trigger:
manual
When you select the user category, the designer automatically creates a context variable named user of type User and selects it as the subject variable. Every run is tracked against the invited person. Administrators can view all invitation activity for a user via GET /api/users/:id/workflow-activity, and runs can be filtered with GET /api/runs?subjectType=user&subjectId=....
Step 2: Context Variable (Auto-Created)¶
The user variable was automatically created when you selected the user category:
| Field | Value |
|---|---|
| Name | user |
| Type | user |
| Required | Yes (auto-set) |
| Description | "The person to invite" |
If the person does not have an account yet, you have three options:
- Pre-provision manually. Call
POST /api/userswith the email (and optionallyupstreamIssuer/upstreamId/findOrCreate). This creates an unconfirmed user profile that will be linked to their identity on first login. Supply the resulting user ID when starting the workflow. - Auto-provision on run start. Mark the
uservariable withautoProvisionByEmail: truein the designer. Then catalog submitters, schedule triggers, and API callers can pass the recipient's email address (or{ email, displayName?, upstreamIssuer?, upstreamId? }) in place of a UUID — the engine will create an unconfirmed row before the run starts. The caller must hold theworkflow:provision_userspermission. Expandeduservariables carry aloginUrlfield (e.g.{{user.loginUrl}}) that deep-links the recipient straight to their IdP on first login. - Provision mid-run with a
user_createstep. When the recipient's email is only learned after the run starts (e.g. from adocument_submissionstep or a connector'sfindUserresponse), insert auser_createstep that takes{{<source>.email}}and writes the new user id into the variable bag (userCreateUserId). Subsequent steps reference the new account by that variable. Everyuser_createexecution requires the run initiator to holdworkflow:provision_users— the gate fires before the email lookup, so the same permission is required on both the create path and the existing-email reuse path. Outcomes — for examplecreated,reused,denied_missing_permission,denied_invalid_config,denied_if_exists_fail,created_attribute_write_failed, andreused_attribute_write_failed— are captured asworkflow.user_auto_provisionedaudit rows. (One outcome —denied_no_initiator— surfaces inStepResult.diagnosticsonly and cannot be audited because the engine never resolves an actor for the row; it indicates a corrupted run record and should be alerted on at the workflow run-status level.)
Step 3: Add the Steps¶
The workflow starts with Start and End nodes already present. Add four steps between them (via the "Add Step" button or the graph editor):
- "Send Invitation" — type:
notification - "Check Response" — type:
condition - "Send Welcome Email" — type:
notification - "Send Sorry Email" — type:
notification
Step 4: Configure Each Step¶
4a. "Send Invitation" — Notification¶
| Config Field | Value |
|---|---|
| Recipient Type | Internal User |
| Recipient User | {{user.id}} |
| Subject Override | e.g. "You've been invited!" |
| Custom Body | (optional — or select an email template) |
| Require User Acceptance | Checked (true) |
| Acceptance Expiry (hours) | e.g. 72 |
Since the invited user is a system user, select Internal User and reference their ID via a variable. The server resolves their email from the user record. This ensures in-app notifications and invitation token matching work correctly.
When "Require User Acceptance" is enabled, the system creates an invitation token and sends the recipient a link. The workflow pauses with status waiting_acceptance until the recipient clicks the link and chooses Accept or Reject.
4b. "Check Response" — Condition¶
| Config Field | Value |
|---|---|
| Expression | acceptance_status == "accepted" |
| True → Go To | Send Welcome Email |
| False → Go To | Send Sorry Email |
The condition evaluates the acceptance result from the previous step. If the recipient accepted, the workflow follows the true branch; if rejected (or expired), it follows the false branch.
4c. "Send Welcome Email" — Notification¶
| Config Field | Value |
|---|---|
| Recipient Type | Internal User |
| Recipient User | {{user.id}} |
| Subject Override | e.g. "Welcome aboard!" |
| Custom Body | Your welcome message |
| Require User Acceptance | Unchecked (false) |
This is a fire-and-forget notification — no acceptance needed.
4d. "Send Sorry Email" — Notification¶
| Config Field | Value |
|---|---|
| Recipient Type | Internal User |
| Recipient User | {{user.id}} |
| Subject Override | e.g. "We're sorry to see you go" |
| Custom Body | Your sorry/farewell message |
| Require User Acceptance | Unchecked (false) |
Step 5: Wire the Graph¶
Connect the steps in the graph editor:
Start
└─→ Send Invitation (notification, requiresAcceptance)
└─→ Check Response (condition)
├─ true → Send Welcome Email → End
└─ false → Send Sorry Email → End
In the graph editor:
- Click the output port of Send Invitation → click the input port of Check Response
- Click the output port of Check Response → click Send Welcome Email (first connection =
truebranch) - Click the output port of Check Response → click Send Sorry Email (second connection =
falsebranch) - Connect both Send Welcome Email and Send Sorry Email to End
Step 6: Save and Publish¶
- Save the workflow (it starts in
draftstatus) - Publish the workflow — this validates the graph and sets the status to
active
Step 7: Start a Run¶
Start the workflow manually (or via API) and supply the user ID. For user-type variables, the engine resolves the ID to a full user object before execution begins.
Starting a run for someone without an account yet¶
If the recipient does not yet have a Floh profile, the user variable can be set to an email (or an object with identity hints) as long as the variable definition has autoProvisionByEmail: true and the caller holds the workflow:provision_users permission. The engine resolves the email against existing users first, then provisions an unconfirmed row on the fly and returns a full user object on the run — including a loginUrl that pre-selects the right IdP for the recipient's first login.
Bare email:
Email with upstream IdP hints (so the generated {{user.loginUrl}} in the invitation email lands on the right login option):
{
"variables": {
"user": {
"email": "jane@example.com",
"displayName": "Jane Doe",
"upstreamIssuer": "okta",
"upstreamId": "okta-00u5abc123"
}
}
}
Callers that cannot (or do not want to) rely on engine-side provisioning can still pre-provision through the REST API:
# Pre-provision an unconfirmed user with just an email address.
# `findOrCreate: true` makes the call idempotent so automation can
# retry safely.
POST /api/users
{ "email": "jane@example.com", "displayName": "Jane Doe", "findOrCreate": true }
# → { "id": "newly-created-uuid", ... }
Then start the workflow with the returned ID. Because the workflow category is user and the subject variable is user, the engine automatically sets subject_type = "user" and subject_id on the run record regardless of which path you used.
Runtime Behavior¶
- The engine executes the Send Invitation step, which sends an email with an acceptance link to
user.emailand pauses the run (waiting_acceptance). - The recipient opens the link, verifies their identity, and clicks Accept or Reject.
- The invitation service records their decision and the engine resumes.
- The Check Response condition evaluates the acceptance status.
- If accepted, the Send Welcome Email step fires. If rejected, the Send Sorry Email step fires.
- The workflow reaches End and the run is marked
completed.
Notes¶
- The condition expression uses simple operators (
==,!=,>,<,>=,<=) with dot-notation for nested data. - Template variables like
{{user.email}}reference the workflow's context variables and are interpolated at execution time. User variables support dot-notation for attributes:{{user.email}},{{user.id}},{{user.displayName}}, etc. - The acceptance link sent to the recipient points to the
/invitations/verify/:tokenand/invitations/respond/:tokenendpoints. - The acceptance expiry is configurable per notification step (default: 72 hours).