Laravel cookbook · Intermediate
Dispatch events and listeners after the transaction commits
Listeners send emails or sync data for records that are then rolled back.
Approach
Mark events with ShouldDispatchAfterCommit so they're only dispatched once the surrounding transaction
commits, and queue slow listeners with ShouldQueue.
Solution
<?php
// app/Events/OrderPlaced.php
namespace App\Events;
use App\Models\Order;
use Illuminate\Contracts\Events\ShouldDispatchAfterCommit;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class OrderPlaced implements ShouldDispatchAfterCommit
{
use Dispatchable, SerializesModels;
public function __construct(public Order $order) {}
}
<?php
// app/Listeners/SendOrderConfirmation.php
namespace App\Listeners;
use App\Events\OrderPlaced;
use App\Notifications\OrderConfirmation;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendOrderConfirmation implements ShouldQueue
{
public string $queue = 'notifications';
public function handle(OrderPlaced $event): void
{
$event->order->customer->notify(new OrderConfirmation($event->order));
}
}
<?php
use App\Events\OrderPlaced;
use Illuminate\Support\Facades\DB;
DB::transaction(function () use ($cart) {
$order = $cart->checkout();
OrderPlaced::dispatch($order); // held until commit; dropped if the transaction rolls back
});
Pitfalls
- Queued listeners that use
$event->orderrelations without reloading;SerializesModelsrestores the model fresh, but relations must be loaded again. - Doing critical work (like charging cards) in listeners without idempotency.
- Too many listeners per event hiding the business flow; keep core steps explicit in an action class.
Variations
- Set
'after_commit' => trueon the queue connection to make all queued jobs wait for commits. - Use model observers (
#[ObservedBy]) for model lifecycle hooks.
Further reading: Events after transactions
Reference: https://laravel.com/docs/13.x/events#dispatching-events-after-database-transactions
More Laravel recipes
- Authorize actions with policies
- Cache expensive data with stale-while-revalidate
- Call external APIs with timeouts, retries and fakes
- Catch N+1 queries with strict Eloquent mode
- Delete old records automatically with prunable models
- Issue scoped API tokens with Sanctum
- Make queued jobs reliable with retries, backoff and uniqueness
- Prevent race conditions with transactions and row locks