Vercel Logo

Pause between steps with sleep()

Pizzas take time to cook. The workflow has no idea.

If we want it to wait for the kitchen before dispatching a driver, we need a way to pause. In a regular Node app, pausing means a setTimeout and praying the process stays alive. In a serverless function it means... well, you don't pause. Your function dies after 10 seconds.

Workflows have sleep(). You await it like any other promise. The workflow suspends. Vercel stops running anything related to that workflow, no compute, no memory, nothing. When the time elapses, the workflow picks up exactly where it left off.

Today we're going to pause for 30 seconds between the kitchen ack and the next step. Boring on paper. The reason to pay attention: while the workflow is sleeping, we're going to deploy a code change. The workflow should survive it untouched.

Outcome

Add await sleep("30s") to processOrder after the kitchen step. Deploy a change while a workflow is mid-sleep and watch the original run complete cleanly.

Fast Track

  1. Import sleep from workflow in workflows/process-order.ts. Add await sleep("30s") after acknowledgeKitchen.
  2. Deploy. Place an order. While the dashboard shows the workflow suspended, change the confirmation email subject and push another deploy.
  3. Watch the original run finish on the new build without re-running the steps that already completed.

Hands-on exercise

1. Add sleep.

Update workflows/process-order.ts:

workflows/process-order.ts
import { sleep } from "workflow";
import type { Order } from "@/lib/pizza";
import { sendOrderConfirmation } from "./steps/send-order-confirmation";
import { acknowledgeKitchen } from "./steps/acknowledge-kitchen";
 
export async function processOrder(order: Order): Promise<{ orderId: string }> {
  "use workflow";
 
  await sendOrderConfirmation(order);
  await acknowledgeKitchen(order);
  await sleep("30s");
 
  return { orderId: order.id };
}

sleep() accepts a duration string like "30s", "5m", "7 days", or a number of milliseconds. We're using 30 seconds because it's long enough to deploy through.

2. Deploy.

git add .
git commit -m "feat(workflow): pause after kitchen ack"
git push

Wait for Vercel to finish the deploy.

Try It

This is the experiment. Pay attention to the timestamps.

Place an order on your production URL. The workflow runs sendOrderConfirmation, then acknowledgeKitchen, then hits sleep("30s") and suspends.

Open the Workflows dashboard in your Vercel project. The run shows as running with the timeline ending at the sleep:

processOrder              running
  ├─ sendOrderConfirmation  completed
  └─ acknowledgeKitchen     completed
  ─ sleeping 30s

While that's suspended, change something. Open workflows/steps/send-order-confirmation.ts and edit the email subject. Add an emoji. Add "v2". Anything. Then:

git commit -am "tweak: update confirmation subject"
git push

Vercel builds and ships the new version.

Now go back to the original run. The sleep elapses. The workflow wakes up on the new deploy. It finishes:

processOrder              completed   30.4s
  ├─ sendOrderConfirmation  completed   389ms
  └─ acknowledgeKitchen     completed   208ms
       (slept 30s, resumed after deploy)

The completed steps don't re-run. The new code is now serving new orders, but the old order finishes on the timeline it started with.

This is the unfair part of durable execution. It's also why the directives exist.

What just happened

Two things kept the original workflow safe across a deploy: every step's input/output was already persisted to the event log, and sleep() doesn't hold a process open. When Vercel restarted the workflow on the new deploy, it replayed the workflow function from the top, hit each completed step, looked up the recorded result, and skipped ahead. It only actually executed code starting from where the workflow had paused.

Commit

feat(workflow): pause after kitchen with sleep()

Done-When

  • workflows/process-order.ts imports sleep from workflow
  • The workflow awaits sleep("30s") after acknowledgeKitchen
  • You've shipped a code change while a workflow was mid-sleep
  • The original run completed on the new deploy without re-running prior steps

Solution

workflows/process-order.ts
import { sleep } from "workflow";
import type { Order } from "@/lib/pizza";
import { sendOrderConfirmation } from "./steps/send-order-confirmation";
import { acknowledgeKitchen } from "./steps/acknowledge-kitchen";
 
export async function processOrder(order: Order): Promise<{ orderId: string }> {
  "use workflow";
 
  await sendOrderConfirmation(order);
  await acknowledgeKitchen(order);
  await sleep("30s");
 
  return { orderId: order.id };
}

One line. Workflow sleeps. Deploys don't matter. This is the closest thing this course has to a magic trick.

Was this helpful?

supported.