Skip to content
Run the businessDurable operations

Background jobs

Big jobs run in the background, finish reliably, and never get lost.

Exports, imports, bulk actions: hand them to the background and move on. Users see honest status the moment work is accepted, live until it's done. If something gets stuck, it shows up as fixable instead of quietly disappearing.

1
step to accept a job for good
6
honest job states, no fake spinners
3
levels of who can see each job

Product example

See it working

This is the same visual component used by the authenticated app, rendered with deterministic sample data.

Scenario
viewport
surface theme
mode

Running

Follow current stage, progress, item counts, milestones, and the available cancel command.

Inert sample data · no network access

Current state

archive notes

In progress

Archiving documents in replay-safe batches

Archiving documents in replay-safe batches684 of 1000
x

Results

Confirmed item outcomes reported by this process.
Successful
680
Failed
4
Skipped
0

Preview ready

The problem this solves

Without a shared foundation, every team rebuilds these edge cases — differently.

The export that never existed

A queue call fails silently during a blip. The customer asks where their export went, and there's no record it was ever accepted. You can't fix what you can't prove happened.

The progress bar on a timer

With no real job record, the UI guesses. Spinners that never resolve. Progress bars driven by setTimeout. A status page that contradicts what the worker actually did.

The slow job killed twice

A naive health check calls slow work dead and re-runs it. Now there are duplicates, and untangling them eats your afternoon. Telling 'late' apart from 'lost' is exactly the code nobody wants to write twice.

Highlights

Why it matters

Accepted means recorded

The job and its record save in one step. Users see 'queued' instantly, even if the queue server is down.

Honest progress

Queued, running, waiting, done, failed, canceled. Real states reported by the worker, not a spinner running on a timer.

Nothing dies in silence

Retries are finite. Work that keeps failing lands in an operator view where one click safely requeues it.

How it works

The mechanism end to end, as implemented in the repository.

  1. 1

    Accept in one step

    The server checks the request, then saves the job, its status record, its follow-up event, and its receipt together in one transaction. The user sees 'queued' right away.

  2. 2

    Hand off safely

    A background publisher moves the job to the queue after the save. Attempts are limited, and jobs that keep failing land in a visible dead state instead of retrying forever.

  3. 3

    Work with receipts

    Workers update the record at each real checkpoint. Big files go to storage; the queue only carries small references.

  4. 4

    Stream the truth

    Status streams live to the user, scoped to their company. Late work reads as 'waiting', not 'failed', and the done notification can never send twice.

Engineering guarantees

What this does and does not promise

Safety boundary

The public demo runs on canned sample data. Real end-to-end behavior still needs your database, sync, and queue services configured, with migrations applied. A passing local check isn't proof of production.

Implementation evidence

The boundary above is code, not a claim. These files carry the contract:

  • docs/PROCESS-SYSTEM.md
  • src/features/processes/lifecycle.ts
  • src/features/processes/realtime-shape.server.ts
Questions

Frequently asked questions

Build on the background jobs foundation

The docs describe the same contracts this page demonstrates.