The HTTP request should commit the fact the user asked for: the booking, the order, the approval. Mail, a PDF, a call to another service, a webhook, an export — those can fail, retry, and take seconds. They do not belong between the click and the response.
Laravel’s queue documentation is the mechanism. The cut is a design rule: the request records the business event. A worker does the slow work.
What stays in the request
Validation, authorization, and the transaction that writes the row. The response tells the user the event was accepted. If the mail server is down, the booking still exists. The mail retries.
What leaves
- Email and SMS
- PDFs and generated files
- Outbound webhooks and provider API calls
- Reports and exports
- Anything you already describe as “and then we notify…”
How we build and API-first backends mention queues in passing. This is the cut those sentences assume.
A second run is normal
Workers retry. A job that sends a receipt, creates a row, or calls a provider can run twice. If a second run must not create a second side effect, give that work a key and a unique constraint so the loser stops. The user’s double-click is not the retry system. The queue is.
If you cannot see failed jobs, you do not have a queue. You have a delay, and the failures are invisible until a customer asks.
What we will not do
- A synchronous HTTP call to a flaky provider inside the controller that just took the booking.
sleep()as a substitute for a worker.- Five queue connections for one app because a diagram had five arrows.
Straight next step
If a user action fails because a third party was slow, tell us which call sits in the request. We will move that call to a job and leave the row committed.



