---
title: "Complete the path"
description: "Add dispatchDelivery, confirmDelivery, and sendReviewRequest steps with sleeps in between so the workflow walks an order from confirmation to review."
canonical_url: "https://vercel.com/academy/workflow-foundations/complete-the-happy-path"
md_url: "https://vercel.com/academy/workflow-foundations/complete-the-happy-path.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-10-20T22:57:37.727Z"
content_type: "lesson"
course: "workflow-foundations"
course_title: "Build Durable Workflows"
prerequisites:  []
---

<agent-instructions>
Vercel Academy — structured learning, not reference docs.
Lessons are sequenced.
Adapt commands to the human's actual environment (OS, package manager, shell, editor) — detect from project context or ask, don't assume.
The lesson shows one path; if the human's project diverges, adapt concepts to their setup.
Preserve the learning goal over literal steps.
Quizzes are pedagogical — engage, don't spoil.
Quiz answers are included for your reference.
</agent-instructions>

# Complete the path

# Complete the happy path

Three more steps and the pizza is at Marge's door.

We've got the order confirmed and the kitchen acknowledged. Time to dispatch a driver, wait for the delivery, and ask Marge to rate her Carbonara. Each one is a step function called from the workflow, with a `sleep()` modeling whatever the real-world delay would be.

We're padding the timeline with fake sleeps because we don't have driver webhooks yet. That changes in 3.2. For now: sleep, sleep, sleep, like a real Tuesday.

## Outcome

Add `dispatchDelivery`, `confirmDelivery`, and `sendReviewRequest` steps. The workflow now walks an order from "in-the-kitchen" all the way to "review-sent" with sleeps modeling the cook and delivery times.

## Fast Track

1. Create three new step files: `dispatch-delivery.ts`, `confirm-delivery.ts`, `send-review-request.ts`.
2. Update `processOrder` to call each one in sequence with `sleep` between them.
3. Place an order. Wait about a minute. Check your inbox for the review request.

## Hands-on exercise

**1. Write the steps.**

`workflows/steps/dispatch-delivery.ts`:

```ts title="workflows/steps/dispatch-delivery.ts"
import { updateStatus } from "@/lib/orders-store";
import type { Order } from "@/lib/pizza";

export async function dispatchDelivery(order: Order): Promise<void> {
  "use step";

  await new Promise((resolve) => setTimeout(resolve, 200));

  updateStatus(order.id, "out-for-delivery");
}
```

`workflows/steps/confirm-delivery.ts`:

```ts title="workflows/steps/confirm-delivery.ts"
import { updateStatus } from "@/lib/orders-store";
import type { Order } from "@/lib/pizza";

export async function confirmDelivery(order: Order): Promise<void> {
  "use step";

  updateStatus(order.id, "delivered");
}
```

`workflows/steps/send-review-request.ts`:

```ts title="workflows/steps/send-review-request.ts"
import { FatalError } from "workflow";
import { resend, FROM } from "@/lib/resend";
import { updateStatus } from "@/lib/orders-store";
import type { Order } from "@/lib/pizza";

export async function sendReviewRequest(order: Order): Promise<void> {
  "use step";

  const resp = await resend.emails.send({
    from: FROM,
    to: [order.email],
    subject: `How was your ${order.pizza}?`,
    html: `
      <p>Hi ${order.customerName},</p>
      <p>Your pizza has landed. Hit reply and tell us how it was.</p>
      <p>Sal</p>
    `,
  });

  if (resp.error) {
    throw new FatalError(`Resend failed: ${resp.error.message}`);
  }

  updateStatus(order.id, "review-sent");
}
```

Same pattern every time. Async function, directive, do the work, persist the status. The Resend step throws `FatalError` on a Resend error, same logic as the confirmation step.

**2. Update the workflow.**

`workflows/process-order.ts`:

```ts title="workflows/process-order.ts" {5-7,15-18}
import { sleep } from "workflow";
import type { Order } from "@/lib/pizza";
import { sendOrderConfirmation } from "./steps/send-order-confirmation";
import { acknowledgeKitchen } from "./steps/acknowledge-kitchen";
import { dispatchDelivery } from "./steps/dispatch-delivery";
import { confirmDelivery } from "./steps/confirm-delivery";
import { sendReviewRequest } from "./steps/send-review-request";

export async function processOrder(order: Order): Promise<{ orderId: string }> {
  "use workflow";

  await sendOrderConfirmation(order);
  await acknowledgeKitchen(order);
  await sleep("30s");
  await dispatchDelivery(order);
  await sleep("30s");
  await confirmDelivery(order);
  await sendReviewRequest(order);

  return { orderId: order.id };
}
```

A minute of total sleep (`30s` + `30s`), two more emails, a final status of `delivered` then `review-sent`. The workflow now describes the entire happy path of an order.

## Try It

Place an order on your production URL. Sit with it. The page polls every couple of seconds, so the status text will tick through the states:

```
0:00   Order placed
0:01   Confirmation email sent
0:02   In the kitchen
0:32   Out for delivery
1:02   Delivered
1:03   Review request sent
```

You should get two emails total: the confirmation right after placing, and the review request about a minute later.

Open the Workflows dashboard and click into the run. The timeline now shows the full sequence:

```
processOrder                completed   62.4s
  ├─ sendOrderConfirmation    completed   389ms
  ├─ acknowledgeKitchen       completed   208ms
  ├─ (sleep 30s)
  ├─ dispatchDelivery         completed   213ms
  ├─ (sleep 30s)
  ├─ confirmDelivery          completed   78ms
  └─ sendReviewRequest        completed   341ms
```

That's six steps and two sleeps, suspended and resumed twice, no infrastructure managed by you.

\*\*Warning: The sleeps are temporary\*\*

Real ordering systems don't pretend cook time is 30 seconds. The whole reason hooks exist is so the workflow can pause until the kitchen actually says it's done. In 3.1 we replace these sleeps with hooks. Don't get attached to them.

## Commit

```
feat(workflow): complete happy path with delivery and review
```

## Done-When

- [ ] Three new step files exist: `dispatch-delivery.ts`, `confirm-delivery.ts`, `send-review-request.ts`
- [ ] `processOrder` calls all five steps in order with sleeps between the cook and delivery phases
- [ ] Placing an order eventually triggers both the confirmation email and the review email
- [ ] The dashboard timeline shows the full sequence ending in `delivered` then `review-sent`

## Solution

`workflows/process-order.ts`:

```ts title="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";
import { dispatchDelivery } from "./steps/dispatch-delivery";
import { confirmDelivery } from "./steps/confirm-delivery";
import { sendReviewRequest } from "./steps/send-review-request";

export async function processOrder(order: Order): Promise<{ orderId: string }> {
  "use workflow";

  await sendOrderConfirmation(order);
  await acknowledgeKitchen(order);
  await sleep("30s");
  await dispatchDelivery(order);
  await sleep("30s");
  await confirmDelivery(order);
  await sendReviewRequest(order);

  return { orderId: order.id };
}
```

The full step files appear in the hands-on section above. Read them once, paste them once, and let the workflow narrate the rest. Next section we replace the sleeps with hooks that wait for the actual kitchen and driver to ping us.


---

[Full course index](/academy/llms.txt) · [Sitemap](/academy/sitemap.md)
