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
- Create three new step files:
dispatch-delivery.ts,confirm-delivery.ts,send-review-request.ts. - Update
processOrderto call each one in sequence withsleepbetween them. - 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:
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:
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:
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:
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.
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 processOrdercalls 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
deliveredthenreview-sent
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";
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.
Was this helpful?