Skip to content

Workflows & Notifications

Workflows automate multi-step processes in your application. They can be triggered by entity events, manual actions from the UI, or webhooks.

[workflow.SetIssueStatus]
description = "Update issue status and write an audit event."
[workflow.SetIssueStatus.trigger]
manual = true
[[workflow.SetIssueStatus.steps]]
type = "query"
entity = "Issue"
action = "update"
[workflow.SetIssueStatus.steps.where]
id = "{{ variables.data.record.id }}"
[workflow.SetIssueStatus.steps.data]
status = "{{ variables.data.payload.status }}"

Triggered from the UI via action bar buttons on detail pages:

[workflow.ApproveRequest.trigger]
manual = true

Wire it to a page action bar:

[[page."/issues/:id".actionBar.actions]]
label = "Approve"
workflow = "ApproveRequest"
style = "primary"

Fire when records are created, updated, or deleted:

[workflow.NotifyOnDone.trigger]
entity = "Issue"
event = "update"
[workflow.NotifyOnDone.trigger.condition]
"after.status" = "done"
"before.status" = { "$ne" = "done" }

The condition uses before.* and after.* to compare the record’s state before and after the change. The $ne operator means “not equal”.

Receive events from external systems:

[workflow.HandleSlackCommand.trigger]
webhook = "/webhooks/slack"

Perform database operations:

[[workflow.MyWorkflow.steps]]
type = "query"
entity = "Issue"
action = "update"
[workflow.MyWorkflow.steps.where]
id = "{{ variables.data.record.id }}"
[workflow.MyWorkflow.steps.data]
status = "closed"

Actions: create, update, delete, findOne.

Branch based on previous step results:

[[workflow.MyWorkflow.steps]]
type = "condition"
if = { "steps.0.result.id" = { "$exists" = true } }
then = [
{ type = "query", entity = "Issue", action = "update", where = { id = "{{ variables.data.record.id }}" }, data = { status = "awaiting_approval" } }
]
else = [
{ type = "query", entity = "AuditEvent", action = "create", data = { issueId = "{{ variables.data.record.id }}" } }
]

Send notifications via configured adapters:

[[workflow.MyWorkflow.steps]]
type = "notify"
adapter = "slack_dispatch"
body = "Issue resolved: {{ trigger.after.title }}"
[workflow.MyWorkflow.steps.metadata]
mrkdwn = true

Make HTTP requests to external services:

[[workflow.MyWorkflow.steps]]
type = "webhook"
url = "https://api.example.com/events"
method = "POST"
body = { issueId = "{{ variables.data.record.id }}" }

Workflow steps can reference data using {{ }} template syntax:

VariableDescription
variables.data.recordThe current record
variables.data.payloadData sent with the action
trigger.beforeRecord state before update (event triggers)
trigger.afterRecord state after update (event triggers)
steps.N.resultResult of step N (zero-indexed)

Notifications are delivered through adapters. Configure them in the [notifications] section.

Logs to stdout (useful for development):

[[notifications.adapters]]
name = "console"
type = "console"

Posts messages to Slack channels:

[[notifications.adapters]]
name = "slack_ops"
type = "slack"
[notifications.adapters.config]
botTokenEnv = "SLACK_BOT_TOKEN"
signingSecretEnv = "SLACK_SIGNING_SECRET"
defaultChannel = "#ops"
ConfigDescription
botTokenEnvEnvironment variable holding the Slack bot token
signingSecretEnvEnvironment variable for webhook signing secret
defaultChannelDefault channel for messages
defaultChannelEnvEnv var for default channel

The Slack adapter also handles inbound webhooks for interactive messages.

Sends email notifications:

[[notifications.adapters]]
name = "email"
type = "email"
[notifications.adapters.config]
from = "noreply@example.com"
outboxFile = "./data/email-outbox.log"
[notifications]
default = "console"

The default adapter is used when a notify step doesn’t specify an adapter.