A job dispatched inside a transaction can be picked up by a worker before the transaction commits, and the worker then cannot find the row it was told about.
DB::transaction(function () use ($order) {
$order->save();
dispatch(new SendReceipt($order->id)); // ← the worker may win
});
// Laravel 8: per connection
// 'after_commit' => true
// or per dispatch
dispatch(new SendReceipt($order->id))->afterCommit();
The failure is timing-dependent, so it appears under load and never in testing — which is what makes it expensive to diagnose. It is also worse with a fast queue: a Redis driver and a local worker will beat the commit far more often than a database queue will. The after_commit connection setting is the right default and the per-dispatch method is the exception for anything that must go immediately.