Wait Step
Pause workflow execution for a duration or until a timestamp
The wait step pauses workflow execution for a fixed duration or until a specific point in time. The workflow is suspended durably -- the process does not block or hold resources. A delayed BullMQ job fires at the right moment to resume execution.
Schema
| Field | Type | Required | Description |
|---|---|---|---|
type | "wait" | Yes | Step type discriminator |
name | string (1-100 chars) | Yes | Unique step name within the workflow |
durationMs | number | No* | Milliseconds to wait from when the step starts |
untilTimestamp | string (ISO 8601) | No* | Absolute datetime to wait until |
retry | RetryPolicy | No | Retry policy for failed executions |
*One of durationMs or untilTimestamp is required. If neither is provided, the step fails with an error.
Configuration Examples
Wait for a fixed duration
Pause for 30 minutes before sending a follow-up:
{
"type": "wait",
"name": "cool-down-period",
"durationMs": 1800000
}Wait until a specific timestamp
Pause until a scheduled launch time:
{
"type": "wait",
"name": "wait-for-launch",
"untilTimestamp": "2026-04-15T09:00:00.000Z"
}How It Works
The wait step does not block a worker thread. Instead, it leverages BullMQ delayed jobs to schedule a precise resume.
Execution flow
-
The execution engine calls
executeWaitStep, which computes awaitUntilDate:- If
untilTimestampis provided, it is parsed directly. - If
durationMsis provided, it is added toDate.now().
- If
-
The step returns
{ status: 'waiting', waitUntil }. -
The engine transitions the step to
WAITINGand the workflow toWAITING. -
A delayed BullMQ job is enqueued with a
delayequal to the remaining milliseconds:delay = Math.max(0, waitUntil.getTime() - Date.now()) -
The job ID is set to
resume-{workflowRunId}-{stepName}to ensure idempotency -- if the same wait is processed twice, only one delayed job exists. -
When the delay elapses, BullMQ fires the job. The engine detects the step is in
WAITINGstatus with typewait, marks itCOMPLETED, and advances to the next step.
Durable guarantees
- Server restarts do not lose the timer. The delayed job is persisted in Redis by BullMQ and survives worker restarts.
- Duplicate processing is safe. The engine checks
stepRun.status === 'WAITING'before completing the step. If the step was already completed (e.g., a duplicate delivery), it skips and advances. - Workflow events are recorded at each transition:
WORKFLOW_PAUSEDwhen the wait begins andSTEP_COMPLETEDwith{ resumedFromWait: true }when it ends.
Practical Usage
Wait steps are commonly used to:
- Rate-limit outreach -- space out emails or API calls to avoid throttling
- Schedule actions -- send a notification at a specific time
- Cool-down periods -- wait after an error before retrying a higher-level workflow
- SLA timers -- wait for a business-hours window before escalating
Example: drip campaign with delays
{
"name": "drip-campaign",
"version": "1.0",
"steps": [
{
"type": "task",
"name": "send-welcome-email",
"handler": "sendEmail",
"config": { "template": "welcome" }
},
{
"type": "wait",
"name": "wait-2-days",
"durationMs": 172800000
},
{
"type": "task",
"name": "send-follow-up",
"handler": "sendEmail",
"config": { "template": "follow-up" }
}
]
}