Why You Need a Job Queue
The moment a request handler starts doing slow work, you have a queue-shaped problem
A request handler exists to do one thing: respond fast. The moment it starts sending an email, calling an AI model, resizing an image, or talking to a flaky third-party API, it stops being fast — and every one of those operations can take anywhere from 500ms to 30 seconds.
What happens without a queue
- The user waits — A "create account" request that also sends a welcome email now takes as long as the slowest of the two
- Timeouts cascade — Most platforms kill HTTP requests after 10–30 seconds — a slow PDF export just becomes a 504 error
- One retry becomes five side effects — The user hits refresh because nothing happened — now you have sent the welcome email five times
- A flaky dependency takes you down with it — If your email provider is slow today, every signup on your site is slow today too
The restaurant kitchen analogy
A queue is the kitchen ticket rail
A waiter does not cook your meal at your table while you wait awkwardly in silence — they take the order, clip the ticket to the rail, and immediately move to the next table. The kitchen works through tickets at its own pace and plates come out when they are ready. The queue is that ticket rail: it lets the front of house (your API) stay fast and responsive while the kitchen (a worker process) handles the slow part separately.
What moves to a background job
- Sending email or SMS — Network calls to a third-party provider that can be slow or rate-limited
- Processing uploads — Resizing images, transcoding video, extracting text from PDFs
- Calling AI models — LLM and image-generation calls routinely take 5–60 seconds
- Webhooks you send to other systems — You do not control how long someone else's server takes to respond
- Anything that touches many rows — Bulk exports, recalculating analytics, sending a newsletter to 50,000 subscribers
The test: "would the user notice if this took 10 seconds?"
If the answer is no — they just need to know it eventually happened — it belongs in a background job, not the request/response cycle.
Try this
Open one of your own API routes that does more than read/write a single database row (e.g. signup, checkout, upload). List every operation inside it and mark each one "must finish before responding" or "could happen after". If anything is marked "could happen after" and takes more than a few hundred milliseconds, that is your first candidate for a background job.