DEV Community

Cover image for How to Send Email Using Queue in Laravel 13 (Mail::queue Tutorial)
Muhammad Yasir Amin
Muhammad Yasir Amin

Posted on Originally published at yasiramin.dev

How to Send Email Using Queue in Laravel 13 (Mail::queue Tutorial)

To send email through a queue in Laravel 13, set QUEUE_CONNECTION to a real driver such as database or redis, create a mailable, send it with Mail::to($user)->queue(new WelcomeMail($user)), and run php artisan queue:work. The web request returns immediately while the worker delivers the message.

What do you need before you start?

  • Laravel 13 on PHP 8.3 or newer
  • Working mail settings in .env (a local tool such as Mailpit or Mailtrap is fine)
  • A queue backend. The database driver needs no extra software

How do you configure the queue?

# .env
QUEUE_CONNECTION=database
MAIL_MAILER=smtp
MAIL_HOST=127.0.0.1
MAIL_PORT=1025
Enter fullscreen mode Exit fullscreen mode

The database driver stores jobs in a jobs table. New Laravel apps ship with the migration, so migrating is enough.

php artisan migrate
Enter fullscreen mode Exit fullscreen mode

How do you create the mailable?

php artisan make:mail WelcomeMail --markdown=mail.welcome
Enter fullscreen mode Exit fullscreen mode
<?php

namespace App\Mail;

use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;

class WelcomeMail extends Mailable
{
    use Queueable, SerializesModels;

    public function __construct(public User $user)
    {
    }

    public function envelope(): Envelope
    {
        return new Envelope(subject: 'Welcome to our app');
    }

    public function content(): Content
    {
        return new Content(markdown: 'mail.welcome');
    }
}
Enter fullscreen mode Exit fullscreen mode

SerializesModels stores only the model id in the job payload and reloads the model when the job runs.

How do you queue the email?

Call queue() instead of send().

use App\Mail\WelcomeMail;
use Illuminate\Support\Facades\Mail;

public function store(Request $request)
{
    $user = User::create($request->validated());

    Mail::to($user)->queue(new WelcomeMail($user));

    return redirect('/dashboard');
}
Enter fullscreen mode Exit fullscreen mode

To always queue a mailable, implement the ShouldQueue interface on it. Then even send() will queue it.

use Illuminate\Contracts\Queue\ShouldQueue;

class WelcomeMail extends Mailable implements ShouldQueue
{
    // ...
}
Enter fullscreen mode Exit fullscreen mode

How do you delay or route the email?

Mail::to($user)->later(now()->addMinutes(10), new WelcomeMail($user));

$mail = (new WelcomeMail($user))->onConnection('redis')->onQueue('emails');
Mail::to($user)->queue($mail);
Enter fullscreen mode Exit fullscreen mode

How do you run the worker?

php artisan queue:work
Enter fullscreen mode Exit fullscreen mode

Restart workers after each deploy with php artisan queue:restart, because workers keep the old code in memory. In production, run the worker under a process manager such as Supervisor.

How do you handle failed emails?

Failed jobs are written to the failed_jobs table. Inspect and retry them from the command line.

php artisan queue:failed
php artisan queue:retry all
Enter fullscreen mode Exit fullscreen mode

How do you verify it works?

  1. Submit the form that sends the mail and confirm the response is fast
  2. Check that a row appears in the jobs table
  3. Run php artisan queue:work and confirm the message appears in your mail inbox tool
  4. In tests, use Mail::fake() and Mail::assertQueued(WelcomeMail::class)
public function test_welcome_mail_is_queued(): void
{
    Mail::fake();

    $this->post('/register', [
        'name' => 'Ana',
        'email' => 'ana@example.com',
        'password' => 'password123',
        'password_confirmation' => 'password123',
    ]);

    Mail::assertQueued(WelcomeMail::class);
}
Enter fullscreen mode Exit fullscreen mode

What are the common mistakes?

  • Leaving QUEUE_CONNECTION=sync, which sends mail inside the request
  • Forgetting to start the worker, so jobs pile up
  • Not restarting workers after changing mail code or config
  • Queueing a mailable that holds a non-serializable object

FAQ

Should I use queue() or ShouldQueue?

Use ShouldQueue if the mail should never block a request. Use queue() when you decide per call.

Does Mail::queue work with the sync driver?

Yes, but it runs immediately in the same request, so you get no speed benefit.

How many times is a failed email retried?

That depends on the worker --tries option or the job attempts setting. Set it explicitly for production.

References


Originally published on yasiramin.dev.

Top comments (0)