Skip to main content
Workflow.sleep and Workflow.sleepUntil pause workflow execution in a fully durable way. Unlike Effect.sleep, these primitives persist the wake-up time to Durable Object storage and schedule a Cloudflare alarm to resume. The workflow picks up exactly where it left off — even across deployments and server restarts.

Workflow.sleep

Pause the workflow for a relative duration.

Signature

function sleep(
  duration: string | number
): Effect.Effect<void, PauseSignal | StorageError | WorkflowScopeError | StepScopeError, ...>

Parameters

duration
string | number
required
How long to sleep. Accepts a human-readable duration string or a number of milliseconds.String formats:
FormatExample
Seconds"30 seconds", "30s", "30 sec"
Minutes"5 minutes", "5m", "5 min"
Hours"2 hours", "2h", "2 hr"
Days"7 days", "7d", "7 day"
Milliseconds"500ms", "500 milliseconds"
Number: milliseconds, e.g. 5000 for 5 seconds.

Examples

// Rate limiting: short delay between batches
yield* Workflow.sleep("30 seconds");

// Wait before sending a follow-up email
yield* Workflow.sleep("24 hours");

// Subscription renewal cycle
yield* Workflow.sleep("30 days");

// Using milliseconds
yield* Workflow.sleep(5000);

Workflow.sleepUntil

Pause the workflow until an absolute Unix timestamp (milliseconds).

Signature

function sleepUntil(
  timestamp: number
): Effect.Effect<void, PauseSignal | StorageError | WorkflowScopeError | StepScopeError, ...>

Parameters

timestamp
number
required
Unix timestamp in milliseconds at which the workflow should resume. If the timestamp is already in the past, the sleep is skipped and execution continues immediately.

Example

// Sleep until a specific point in time
const renewAt = new Date("2026-01-01T00:00:00Z").getTime();
yield* Workflow.sleepUntil(renewAt);

// Dynamically computed from step result
const order = yield* Workflow.step({
  name: "Fetch order",
  execute: fetchOrder(orderId),
});

yield* Workflow.sleepUntil(order.scheduledAt);

How durable sleep works

When Workflow.sleep is called:
  1. The wake-up timestamp is persisted to Durable Object storage.
  2. A Cloudflare Durable Object alarm is scheduled for the wake-up time.
  3. The workflow pauses execution by emitting a PauseSignal.
  4. When the alarm fires, the workflow resumes and the sleep is marked complete.
  5. On subsequent replays, the completed sleep is detected from storage and skipped.
This means your workflow can sleep for seconds or months — infrastructure restarts, deployments, and cold starts have no effect on when it wakes up.
Workflow.sleep and Workflow.sleepUntil can only be called at the top level of a workflow, not inside a Workflow.step. Calling them inside a step will produce a compile-time error via the WorkflowLevel context guard.

DurationInput type

Both sleep and timeout in Workflow.step accept the DurationInput type:
type DurationInput = string | number;
String values are parsed by parseDuration. Supported units:
parseDuration("500ms")       // 500
parseDuration("30 seconds")  // 30_000
parseDuration("5 minutes")   // 300_000
parseDuration("2 hours")     // 7_200_000
parseDuration("7 days")      // 604_800_000
parseDuration("30 days")     // 2_592_000_000
parseDuration(5000)          // 5000 (number passthrough)

Full workflow example

import { Effect } from "effect";
import { Workflow } from "@durable-effect/workflow";

const onboardingWorkflow = Workflow.make((userId: string) =>
  Effect.gen(function* () {
    yield* Workflow.step({
      name: "Send welcome email",
      execute: sendWelcomeEmail(userId),
    });

    // Wait 24 hours before follow-up
    yield* Workflow.sleep("24 hours");

    yield* Workflow.step({
      name: "Send follow-up email",
      execute: sendFollowUpEmail(userId),
    });

    // Wait 7 days before check-in
    yield* Workflow.sleep("7 days");

    yield* Workflow.step({
      name: "Send check-in email",
      execute: sendCheckInEmail(userId),
    });
  })
);

Build docs developers (and LLMs) love