Challenge 2: Make a Listener Queued — Possible Solution ==================================================================== // BEFORE — runs synchronously namespace App\Listeners; use App\Events\UserRegistered; use Illuminate\Support\Facades\Mail; class SendWelcomeEmail { public function handle(UserRegistered $event): void { Mail::to($event->user->email)->send( new \App\Mail\WelcomeEmail($event->user) ); } } // AFTER — runs on the queue namespace App\Listeners; use App\Events\UserRegistered; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Support\Facades\Mail; class SendWelcomeEmail implements ShouldQueue { public function handle(UserRegistered $event): void { Mail::to($event->user->email)->send( new \App\Mail\WelcomeEmail($event->user) ); } } WHAT CHANGES ABOUT WHEN THE EMAIL ACTUALLY GETS SENT --------------------------------------------------------- Without ShouldQueue, when UserRegistered::dispatch($user) is called inside the registration controller, SendWelcomeEmail::handle() runs IMMEDIATELY, synchronously, as part of that same HTTP request — the registering user's browser waits for the full SMTP round trip (connecting to the mail server, sending the email) to complete before the redirect response is even sent back to them. With ShouldQueue added, dispatch($user) still runs the SAME line of code, but Laravel instead pushes a queued job onto the configured queue connection (Redis, database, etc.) and returns control to the controller IMMEDIATELY — the HTTP response (the redirect to the dashboard) is sent back to the browser right away, and the actual email-sending work happens moments later, on a separate queue worker process, completely outside the original request/response cycle. WHY THIS WORKS -------------- - implementing ShouldQueue is a marker interface Laravel's event dispatcher checks for — no other code changes anywhere are required; the exact same handle() method body runs in both cases, just on a different "thread" of execution (inline vs a queue worker). - This is a genuinely cleaner integration than the Django course's Celery chapter needed: there, upgrading a synchronous post_save signal receiver to be non-blocking required writing a SEPARATE @shared_task function and calling send_welcome_email.delay(user.pk) from inside the signal — two files, two concepts. Here, the SAME listener class becomes queue-backed just by implementing one interface. - This directly mirrors the "returns immediately" benefit from background-task chapters throughout this project — the user-facing request no longer waits on a slow, unrelated side effect (sending an email) before it can complete.