<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/vercel-workflows-tutorial-build-a-durable-background-job-27npsfaqy" -->

---
title: Vercel Workflows tutorial: build a durable background job
description: Vercel Workflows enables durable background jobs that survive failures, sleeps, and deployments by recording step progress. Using 'use workflow' and 'use step'...
canonical: https://daily.dev/posts/vercel-workflows-tutorial-build-a-durable-background-job-27npsfaqy
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Vercel Workflows tutorial: build a durable background job | daily.dev
og:description: Vercel Workflows enables durable background jobs that survive failures, sleeps, and deployments by recording step progress. Using 'use workflow' and 'use step'...
og:url: https://daily.dev/posts/vercel-workflows-tutorial-build-a-durable-background-job-27npsfaqy
og:image: https://api.daily.dev/og/posts/27npsFaQy.png
og:image:alt: Vercel Workflows tutorial: build a durable background job
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Vercel Workflows tutorial: build a durable background job

**[Flavio Copes](https://daily.dev/sources/flaviocopes)** · 6 min read · 0 upvotes · 1 comments

## Summary

Vercel Workflows enables durable background jobs that survive failures, sleeps, and deployments by recording step progress. Using 'use workflow' and 'use step' directives in TypeScript, you can build multi-step async processes like customer onboarding that include automatic retries, idempotency handling, and long sleeps without holding compute. The tutorial walks through creating a Next.js app with the Workflow SDK, triggering workflows via an API route, inspecting runs locally, handling transient vs. permanent failures with FatalError, and deploying to Vercel where workflows remain pinned to their originating deployment even across code changes.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://flaviocopes.com/vercel-workflows>

## Questions this post answers

### How do I make a background job survive a Vercel deployment or restart in the middle of a multi-day wait?

Use Vercel Workflows instead of setTimeout or a plain serverless function. A workflow marked with 'use workflow' records completed step results and can sleep (for example await sleep('3d')) without holding compute; if a deployment happens while it sleeps, the running workflow stays attached to the deployment that started it and finishes with the original code, while new runs use the new deployment.

_Developers building long-running jobs track patterns like this durable-workflow approach on daily.dev._

### How do I stop a Vercel Workflow step from retrying when the failure is permanent, like an invalid email address?

Throw a FatalError instead of a normal Error inside a step marked 'use step'. Normal errors trigger automatic retries, but Vercel Workflows treats FatalError as unrecoverable and stops retrying immediately, which is appropriate for input validation failures such as a malformed email address that no retry would fix.

_Anyone designing retry logic for background jobs can compare approaches like this on daily.dev._

### Why do retried steps in Vercel Workflows need idempotency keys?

Because a step can execute more than once when it retries after a failure, and a provider might have accepted a request (like sending an email) right before the connection dropped, making it unsafe to assume an error means the side effect never happened. Passing a stable idempotency key, such as `onboarding-welcome-${customer.id}`, to an email provider or using a unique constraint or upsert for database writes prevents duplicate side effects.

_Teams building reliable retry-safe background jobs follow discussions like this on daily.dev._

## Community discussion

Top comments from developers on daily.dev.

**@agustinbarrientos** · 0 upvotes

> Sleeping for three days without holding compute.

## Similar posts on daily.dev

- [A new programming model for durable execution](https://daily.dev/posts/a-new-programming-model-for-durable-execution-8hk6zrkca) · Vercel · 0 upvotes · 0 comments
- [Vercel Queues tutorial: process background jobs reliably](https://daily.dev/posts/vercel-queues-tutorial-process-background-jobs-reliably-ca8x9vzjh) · Flavio Copes · 4 upvotes · 1 comments
- [Make any TypeScript Function Durable](https://daily.dev/posts/make-any-typescript-function-durable-yvdp3c4nc) · Hacker News · 1 upvotes · 0 comments

---

Tags: [#typescript](https://daily.dev/tags/typescript), [#nextjs](https://daily.dev/tags/nextjs)

[View this post on daily.dev](https://daily.dev/posts/vercel-workflows-tutorial-build-a-durable-background-job-27npsfaqy)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"Vercel Workflows tutorial: build a durable background job","url":"https://daily.dev/posts/vercel-workflows-tutorial-build-a-durable-background-job-27npsfaqy","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/vercel-workflows-tutorial-build-a-durable-background-job-27npsfaqy"},"datePublished":"2026-07-29T18:09:45.478Z","dateModified":"2026-09-14T06:39:09.059Z","description":"Vercel Workflows enables durable background jobs that survive failures, sleeps, and deployments by recording step progress. Using 'use workflow' and 'use step'...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/7219243bb6b6113ecc90bd933965a4ef?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/7219243bb6b6113ecc90bd933965a4ef?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"Flavio Copes","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"Flavio Copes","logo":"https://media.daily.dev/image/upload/t_logo,f_auto/v1/logos/1f2dbd275797411bbd37bfdaaf0951a9","url":"https://daily.dev/sources/flaviocopes"},"commentCount":1,"discussionUrl":"https://daily.dev/posts/vercel-workflows-tutorial-build-a-durable-background-job-27npsfaqy","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":0},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":1}],"keywords":"typescript,nextjs","timeRequired":"PT6M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Flavio Copes","item":"https://daily.dev/sources/flaviocopes"},{"@type":"ListItem","position":3,"name":"Vercel Workflows tutorial: build a durable background job"}]}
{"@context":"https://schema.org","@type":"WebPage","@id":"https://daily.dev/posts/vercel-workflows-tutorial-build-a-durable-background-job-27npsfaqy","comment":[{"@type":"Comment","text":"Sleeping for three days without holding compute.","datePublished":"2026-07-31T17:53:45.488Z","url":"https://daily.dev/posts/27npsFaQy#c-zbOkF4M0Z","author":{"@type":"Person","name":"Agustin Barrientos","url":"https://daily.dev/agustinbarrientos","image":"https://media.daily.dev/image/upload/s--5ayxQnqn--/f_auto/v1788281802/avatars/avatar_wQYYVe5Tbj0NJ7C7qPoa8?_a=BAMAMicg0"}}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/vercel-workflows-tutorial-build-a-durable-background-job-27npsfaqy#faq","mainEntity":[{"@type":"Question","name":"How do I make a background job survive a Vercel deployment or restart in the middle of a multi-day wait?","acceptedAnswer":{"@type":"Answer","text":"Use Vercel Workflows instead of setTimeout or a plain serverless function. A workflow marked with 'use workflow' records completed step results and can sleep (for example await sleep('3d')) without holding compute; if a deployment happens while it sleeps, the running workflow stays attached to the deployment that started it and finishes with the original code, while new runs use the new deployment. Developers building long-running jobs track patterns like this durable-workflow approach on daily.dev."}},{"@type":"Question","name":"How do I stop a Vercel Workflow step from retrying when the failure is permanent, like an invalid email address?","acceptedAnswer":{"@type":"Answer","text":"Throw a FatalError instead of a normal Error inside a step marked 'use step'. Normal errors trigger automatic retries, but Vercel Workflows treats FatalError as unrecoverable and stops retrying immediately, which is appropriate for input validation failures such as a malformed email address that no retry would fix. Anyone designing retry logic for background jobs can compare approaches like this on daily.dev."}},{"@type":"Question","name":"Why do retried steps in Vercel Workflows need idempotency keys?","acceptedAnswer":{"@type":"Answer","text":"Because a step can execute more than once when it retries after a failure, and a provider might have accepted a request (like sending an email) right before the connection dropped, making it unsafe to assume an error means the side effect never happened. Passing a stable idempotency key, such as `onboarding-welcome-${customer.id}`, to an email provider or using a unique constraint or upsert for database writes prevents duplicate side effects. Teams building reliable retry-safe background jobs follow discussions like this on daily.dev."}}]}
```

