Stevora

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

FieldTypeRequiredDescription
type"wait"YesStep type discriminator
namestring (1-100 chars)YesUnique step name within the workflow
durationMsnumberNo*Milliseconds to wait from when the step starts
untilTimestampstring (ISO 8601)No*Absolute datetime to wait until
retryRetryPolicyNoRetry 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

  1. The execution engine calls executeWaitStep, which computes a waitUntil Date:

    • If untilTimestamp is provided, it is parsed directly.
    • If durationMs is provided, it is added to Date.now().
  2. The step returns { status: 'waiting', waitUntil }.

  3. The engine transitions the step to WAITING and the workflow to WAITING.

  4. A delayed BullMQ job is enqueued with a delay equal to the remaining milliseconds:

    delay = Math.max(0, waitUntil.getTime() - Date.now())
  5. The job ID is set to resume-{workflowRunId}-{stepName} to ensure idempotency -- if the same wait is processed twice, only one delayed job exists.

  6. When the delay elapses, BullMQ fires the job. The engine detects the step is in WAITING status with type wait, marks it COMPLETED, 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_PAUSED when the wait begins and STEP_COMPLETED with { 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" }
    }
  ]
}