Skip to content

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:

  1. A Notification step that sends the invitation email (with acceptance required)
  2. A Condition step that checks the recipient's response
  3. 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:

  1. Pre-provision manually. Call POST /api/users with the email (and optionally upstreamIssuer/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.
  2. Auto-provision on run start. Mark the user variable with autoProvisionByEmail: true in 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 the workflow:provision_users permission. Expanded user variables carry a loginUrl field (e.g. {{user.loginUrl}}) that deep-links the recipient straight to their IdP on first login.
  3. Provision mid-run with a user_create step. When the recipient's email is only learned after the run starts (e.g. from a document_submission step or a connector's findUser response), insert a user_create step that takes {{<source>.email}} and writes the new user id into the variable bag (userCreateUserId). Subsequent steps reference the new account by that variable. Every user_create execution requires the run initiator to hold workflow: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 example created, reused, denied_missing_permission, denied_invalid_config, denied_if_exists_fail, created_attribute_write_failed, and reused_attribute_write_failed — are captured as workflow.user_auto_provisioned audit rows. (One outcome — denied_no_initiator — surfaces in StepResult.diagnostics only 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):

  1. "Send Invitation" — type: notification
  2. "Check Response" — type: condition
  3. "Send Welcome Email" — type: notification
  4. "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:

  1. Click the output port of Send Invitation → click the input port of Check Response
  2. Click the output port of Check Response → click Send Welcome Email (first connection = true branch)
  3. Click the output port of Check Response → click Send Sorry Email (second connection = false branch)
  4. Connect both Send Welcome Email and Send Sorry Email to End

Step 6: Save and Publish

  1. Save the workflow (it starts in draft status)
  2. 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.

{
  "variables": {
    "user": "uuid-of-user"
  }
}

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:

{
  "variables": {
    "user": "jane@example.com"
  }
}

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

  1. The engine executes the Send Invitation step, which sends an email with an acceptance link to user.email and pauses the run (waiting_acceptance).
  2. The recipient opens the link, verifies their identity, and clicks Accept or Reject.
  3. The invitation service records their decision and the engine resumes.
  4. The Check Response condition evaluates the acceptance status.
  5. If accepted, the Send Welcome Email step fires. If rejected, the Send Sorry Email step fires.
  6. 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/:token and /invitations/respond/:token endpoints.
  • The acceptance expiry is configurable per notification step (default: 72 hours).