Not a default, a response

Adding a queue means asynchronous behaviour, a new system to run, and failures that happen where nobody is watching. It is worth it when one of a few specific problems appears, and premature otherwise.

Signal 1: the user is waiting for something they don’t need

Sending mail, generating a report, calling a slow third party. If the response could return without it, it belongs in the background — the batch-versus-real-time question applied to ordinary work.

Signal 2: a dependency fails and you lose the work

When an external service being down means a request is simply lost, a queue with retries turns an outage into a delay. That is the same reason webhook handlers store first and process later.

A queue turns “we lost it” into “it arrived late”. That is usually the whole business case.

Signal 3: spikes exceed what you can process live

Bursts that overwhelm a downstream system or a rate limit are absorbed by a queue with controlled concurrency — the standard remedy for AI features under load.

What you take on

Jobs run twice, so handlers must be idempotent. Jobs fail permanently, so you need a dead-letter destination someone actually reads. And the whole thing can silently stop, which needs alerting on absence rather than on errors.

Tell the user what happened

Work moved to the background is invisible unless the interface says so. “We’re processing this, you’ll get an email” prevents the duplicate submissions that otherwise arrive within seconds.