Moving work off the request path trades immediacy for throughput, and that trade has a contract. This spec defines it: what an acknowledgement means, what a job is allowed to carry, how it behaves on retry, and how it fails loudly. Break any clause and you get stale reads, lost writes, or duplicated effects.
Contract
Acknowledgement semantics
A 202 means the work was accepted onto the queue, not that it has been applied. The client must not render the submitted value as settled truth; for data the user will act on, it confirms by reading back from the system of record. Fields whose wrongness is expensive are written synchronously, not queued.
| Response | Means | Client must |
|---|---|---|
| 202 Accepted | Enqueued, not yet applied | Show pending; confirm via a read |
| 200 + body | Applied synchronously | Trust the returned value |
| 4xx | Rejected, never enqueued | Surface the error |
Definition
Job payload rule
A job carries the identifiers it needs to find its data, never a hydrated model. It re-loads current state at execution time, so it acts on what is true when it runs — not on a snapshot frozen at dispatch, which under backlog may be minutes stale. Re-loading also forces the job to handle a record that was deleted in between.
class SendInvoice implements ShouldQueue
{
public function __construct(public int $tenantId, public int $invoiceId) {}
public function handle(): void
{
$invoice = Invoice::find($this->invoiceId); // current state
if ($invoice === null) return; // superseded — don't act
Mail::to($invoice->customer)->queue(new InvoiceIssued($invoice));
}
}Contract
Idempotency and retry
Queues deliver at least once, so every job must be safe to run more than once — idempotent on a stable key. Transient failures retry with exponential backoff; they never retry immediately into a struggling dependency.
| Property | Rule |
|---|---|
| Idempotency | Effect keyed on a stable ID; re-run = no-op |
| Retries | Bounded (`tries`), with exponential backoff |
| Ordering | Not assumed; jobs tolerate out-of-order delivery |
| Poison jobs | Land in the failed store after max tries |
Definition
Dead-letter and observability
A job that exhausts its retries is recorded in a failed-jobs store and pages someone — a silently vanished job is worse than a loudly failed one. A replay path keyed on the job's identity lets you re-drive it safely once fixed, because idempotency makes replay a no-op if it already succeeded.
Invariants this spec guarantees
- A 202 never causes the client to assert a write as applied; critical values are confirmed by a read or written synchronously.
- Jobs carry identifiers and re-load current state; they never act on a snapshot frozen at dispatch.
- Every job is idempotent on a stable key and safe under at-least-once delivery.
- A job that exhausts its retries is visible and alertable — never silently lost.