Laravel jobs and transactions: why the worker cannot find your new record

A request creates an order and immediately dispatches a job. On a fast queue, the worker starts before the database transaction commits. The worker then queries an order that does not yet exist from its connection's point of view.
Put dispatch after the commit
This application example assumes an Order model and a SendOrderReceipt job that implements ShouldQueue and accepts an order ID.
use App\Jobs\SendOrderReceipt;
use App\Models\Order;
use Illuminate\Support\Facades\DB;
$order = DB::transaction(function () use ($validatedOrder) {
$order = Order::create($validatedOrder);
SendOrderReceipt::dispatch($order->id)->afterCommit();
return $order;
});
Validate and authorize the request before this operation. The transaction groups the database work, while the dispatch waits for the outer transaction to commit.
Separate two failure windows
The first failure window is the race between dispatch and commit. Deferring dispatch addresses that ordering problem. The second is a process failure after the database commit but before a message is durably accepted by the queue. For workflows requiring recovery from that gap, investigate a transactional outbox.
An outbox records an event in the same transaction as the business write. A separate dispatcher retries delivery of those stored events. It still needs duplicate handling because acknowledging delivery is another boundary.
Test the ordering that matters
Test a successful commit and a rollback. Check that a rollback does not produce the business effect. A queue fake can establish that your application requests a dispatch; it does not prove the behavior of a running worker with a separate database connection.
Use an integration test against the chosen queue backend for that race. Keep emails faked during local tests, and assert the order identifier reaching the job.
Laravel's queue documentation documents after-commit dispatch behavior. This example is an integration pattern, not a standalone PHP script.