Workflows
Your templates run on the production engine: 16 live triggers, 12 actions, true if/else branching, idempotent runs, DLQ retries and a full audit trail.
Wire shape (typed DSL)
"workflowTemplates": [{
"name": "Hot lead fast-lane",
"nodes": [
{ "id": "t1", "type": "trigger", "config": { "eventType": "lead_created" }, "connections": ["c1"] },
{ "id": "c1", "type": "condition", "config": { "field": "score", "operator": ">=", "value": 70 },
"connections": ["a1"], "elseConnections": ["a2"] },
{ "id": "a1", "type": "create_task", "config": { "title": "Call {{leadName}}", "priority": "high" }, "connections": [] },
{ "id": "a2", "type": "update_field", "config": { "entityType": "lead", "field": "status", "value": "nurturing" }, "connections": [] }
]
}]
Exactly one trigger per workflow; graphs must be acyclic; connections is the true/next path, elseConnections the false path on conditions. {{var}} templates resolve from the event payload.
Trigger catalog
| eventType | fires when | payload highlights |
|---|---|---|
lead_created | a lead is created (any source: UI, API, forms, imports, email auto-capture) | leadId, leadName, leadEmail, leadCompany, score, status, source |
lead_status_changed | lead status updates | + oldStatus, newStatus |
lead_score_changed | scoring engine moves a lead | score |
contact_created | a contact is born (CRM or the contact-intelligence service) | contactId, email, contactName |
deal_won / deal_stage_changed | opportunity outcomes / stage moves | opportunityId, value, stage(s), leadId |
form_submitted | a public lead-capture form submits | formId, formName, submitted fields, created lead |
webhook_received | an external system POSTs the workflow's secret hook URL | the request body (top-level keys) |
scheduled | every intervalMinutes (≥ 5; multi-pod-safe idempotency) | scheduledAt |
email_replied / email_opened | a lead replies to / opens tracked email | leadId, subject |
task_completed / task_overdue | task lifecycle | taskId, title, leadId |
meeting_scheduled | a new future calendar meeting syncs in | meetingId, startTime, leadId |
review_received / review_negative | reputation events | review payload |
Action catalog
| type | config | behaviour |
|---|---|---|
send_email | to?, subject, message | sends via the tenant's connected mailbox; unconfigured tenants get an explanatory skip, never a fake send |
create_task | title, description?, priority?, dueInDays? | real task, assignee from payload when present |
update_field | entityType (lead|opportunity|task), field, value | standard columns or the tenant's CUSTOM fields by api-name |
create_record | entityType (lead|task|activity|opportunity|custom:<apiName>), fields{} | creates records — including into objects your app provisioned |
update_lead_status / assign_lead / add_note / move_deal_stage | status / assignTo / subject+body / stage | classic CRM mutations |
call_webhook | url (https), method?, headers?, body? | POSTs the payload to your service; SSRF-guarded; non-2xx → DLQ retry |
run_agent | agentId, instructions | invokes a Builder agent with the event context |
wait | minutes | durable pause (survives restarts/deploys) |
wait_for_event | eventType, timeoutMinutes? | pauses until a matching domain event for the tenant, or the timeout |
Reliability semantics
- Idempotent runs — one run per (workflow, source event); redeliveries return the existing run.
- DLQ + backoff — failed steps retry 1s → 30s → 2m → 10m → 1h (5 attempts) and surface in the tenant's Failures tab with one-click retry.
- Audit ledger — every run writes
workflow.run(run id, status, duration, steps). - Dry-run before publish — tenants can replay your template over their last 10 real matching records with zero side effects; design templates that read well in that preview.
