Documentation
DocsCore ConceptsWorkflows & Enrollments
Workflows & Enrollments
Build multi-step email sequences with conditional branching, delays, and event triggers. Powered by Temporal for durable execution.
Workflow Structure
A workflow is a directed graph of nodes. Each node performs an action and points to the next node.
WorkflowDefinition
{
id: string,
name: string,
version: number,
status: "active" | "draft" | "paused" | "failed" | "archived",
trigger: "manual" | "api" | "event" | "scheduled",
nodes: WorkflowNode[],
exitCriteria?: ExitCriterion[],
optOutMode?: "link" | "reply" | "header_only", // default: "reply"
schedule?: { // for "scheduled" trigger
cronExpression: string, // e.g. "0 9 * * 1" (every Monday 9am)
contactFilter?: {
segmentId?: string // only enroll contacts in this segment
}
},
pausedNodeIds?: string[], // nodes currently paused via node-level pause
createdAt: string,
updatedAt: string
}Node Types
WorkflowNode
{
id: string,
type: "delay" | "send_email" | "send_webhook" | "wait_for_event" | "branch" | "exit" | "goto" | "ab_test",
label: string,
nextNodeId: string | null,
// delay nodes
delayAmount?: number, // duration amount
delayUnit?: "minutes" | "hours" | "days",
delayMinutes?: number, // legacy, prefer delayAmount + delayUnit
// send_email nodes
templateId?: string,
// send_webhook nodes
webhookUrl?: string, // POST target URL
webhookBody?: string, // Liquid-templated JSON body
webhookAuthHeader?: string, // e.g. "Bearer sk-xxx"
webhookResponseBranches?: [{ // branch on webhook response
condition: { field: string, operator: string, value?: string },
nextNodeId: string
}],
// wait_for_event nodes
waitForEvent?: string,
// branch nodes
branches?: [{
condition: {
field: string, // e.g. "contact.email", "engagement.opened", "contact.attributes.company_size"
operator: "equals" | "contains" | "exists" | "not_equals" | "is_true" | "is_false",
value?: string
},
nextNodeId: string
}],
// goto nodes (loops)
targetNodeId?: string, // jump destination node ID
maxIterations?: number, // safety guard, default 10
// ab_test nodes
variants?: [{
id: string,
templateId: string,
weight: number // relative weight for random selection
}]
}- delay — Pause for
delayAmount+delayUnitbefore continuing (legacydelayMinutesalso supported). - send_email — Send the template specified by
templateId. - wait_for_event — Pause until the named event occurs (e.g.,
message.received). - branch — Evaluate conditions and route to different next nodes. Supports
contact.attributes.*for custom data. - exit — End the workflow.
- send_webhook — POST to
webhookUrlwith a Liquid-templated JSON body. Supports optional auth header and response-based branching. - goto — Jump to
targetNodeId, creating loops. Capped bymaxIterations(default 10) to prevent infinite loops. When exceeded, falls through tonextNodeId. - ab_test — Randomly select a variant by weight and send that template. The selected
variantIdis stored on the enrollment for analytics.
Exit Criteria
Define conditions that automatically remove contacts from a workflow. When an exit criterion is triggered, the enrollment status changes to "exited" with a reason.
ExitCriterion
{
trigger: "reply" | "bounce" | "unsubscribe" | "manual" | "custom_event",
eventName?: string, // required for custom_event
replyClassifications?: string[] // filter which reply types trigger exit (e.g. ["not_interested"])
}Add exit criteria to the workflow definition:
WorkflowDefinition with exitCriteria
{
name: "Outbound Drip",
trigger: "api",
nodes: [...],
exitCriteria: [
{ trigger: "reply" },
{ trigger: "bounce" },
{ trigger: "unsubscribe" },
{ trigger: "custom_event", eventName: "deal.closed" }
]
}Engagement-Based Branching
Branch nodes can evaluate engagement signals using the engagement scope with is_true / is_false operators:
Engagement Branch Example
{
id: "n3", type: "branch", label: "Opened email?",
branches: [
{
condition: { field: "engagement.opened", operator: "is_true" },
nextNodeId: "n4_opened"
}
],
nextNodeId: "n4_not_opened" // default path
}Available engagement fields: engagement.opened, engagement.clicked, engagement.replied.
Workflow Lifecycle
Status Transitions
draft → active (publish) → paused (pause) → active (resume)
→ failed (error)TypeScript
// Create, publish, then pause
const wf = await client.createWorkflow({
name: "Onboarding",
trigger: "api",
nodes: [
{ id: "n1", type: "send_email", label: "Welcome", templateId: "tmpl_1", nextNodeId: "n2" },
{ id: "n2", type: "delay", label: "Wait 2 days", delayAmount: 2, delayUnit: "days", nextNodeId: "n3" },
{ id: "n3", type: "exit", label: "Done", nextNodeId: null }
]
});
await client.publishWorkflow(wf.id); // draft → active
await client.pauseWorkflow(wf.id); // active → paused
await client.resumeWorkflow(wf.id); // paused → activecurl
# Publish a workflow curl -X POST https://api.outreachagent.dev/v1/workflows/wf_abc/publish \ -H "Authorization: Bearer $OUTREACHAGENT_API_KEY" # Pause a workflow curl -X POST https://api.outreachagent.dev/v1/workflows/wf_abc/pause \ -H "Authorization: Bearer $OUTREACHAGENT_API_KEY"
Enrollments
Enrolling a contact in a workflow starts execution. Track progress via the enrollment status and execution logs.
Enrollment
{
id: string,
workflowId: string,
contactId: string,
status: "active" | "completed" | "paused" | "failed" | "exited",
exitReason: string | null,
currentNodeId: string | null,
variantId: string | null, // set by ab_test nodes
variables: Record<string, unknown>, // per-enrollment template overrides ({{ vars.<key> }})
createdAt: string
}ExecutionLog
{
id: string,
enrollmentId: string,
nodeId: string,
status: "scheduled" | "running" | "completed" | "failed" | "skipped",
message: string,
createdAt: string
}TypeScript
// Enroll a contact with per-enrollment variables
const enrollment = await client.createEnrollment({
workflowId: "wf_abc",
contactId: "con_xyz",
variables: { hook: "your recent product launch", offer: "15% discount" }
});
// Check execution logs
const logs = await client.listEnrollmentLogs(enrollment.id);curl
# Enroll a contact with variables
curl -X POST https://api.outreachagent.dev/v1/enrollments \
-H "Authorization: Bearer $OUTREACHAGENT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"workflowId": "wf_abc", "contactId": "con_xyz", "variables": {"hook": "your recent product launch"}}'
# Get execution logs
curl https://api.outreachagent.dev/v1/enrollments/enr_123/logs \
-H "Authorization: Bearer $OUTREACHAGENT_API_KEY"