Courses

Practical Laravel Queues on Live Server

Queue Workers Setup: Send Email Example

In web applications, certain tasks consume a lot of time, like sending an email. With Laravel, you can create queued jobs that can be executed in the background. Then your users will get the response on the screen or from API quicker.

In this first chapter we will show you how to run jobs in the queue on your local web server with Database driver. In Laravel we can enqueue jobs, events, and closures.

The database driver is acceptable if the queue workload is expected to be small and sufficient to handle the traffic. It is a simple queue solution that does not require additional requirements and may be cheaper than other options like Redis or Amazon SQS.

Send Verification Email Demo

Mailtrap Email

  1. For this demonstration we will take the default Laravel installation with Laravel Breeze which will provide all the scaffolding to send email verification.
laravel new test-project
 
cd test-project/
 
composer require laravel/breeze
php artisan breeze:install -q blade

Do not forget to configure database settings in your .env file and run migrations.

  1. As for inbox we chose to use Mailtrap.io platform to capture emails.

Credentials will be provided as you create your email testing inbox:

Mailtrap

Now configure your mail credentials in the .env file:

.env

MAIL_MAILER=smtp
MAIL_HOST=sandbox.smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=<YOUR_USERNAME>
MAIL_PASSWORD=<YOUR_PASSWORD>
MAIL_ENCRYPTION=tls
  1. Now update the User model by uncommenting the MustVerifyEmail contract, and adding the implements MustVerifyEmail statement to the User class.

app/Models/User.php

use Illuminate\Contracts\Auth\MustVerifyEmail;
 
class User extends Authenticatable implements MustVerifyEmail

Now go to the /register URL to create a new user and when the form is submitted an email should show up in your testing inbox.

In this case verification, email is being sent immediately (synchronously). To send email using queues (asynchronously) we need to make a few changes.

  1. Create a jobs table where all queue-ables will be stored.
php artisan queue:table
php artisan migrate

And update the environment file

.env

from

QUEUE_CONNECTION=sync

to

QUEUE_CONNECTION=database

If you forget to do that all your enqueued jobs and events will be run synchronously and won't benefit from queues.

  1. In app/Providers/EventServiceProvider.php comment out SendEmailVerificationNotification::class, line:
protected $listen = [
Registered::class => [
// SendEmailVerificationNotification::class,
],
];

We won't be using the default listener for the Registered event and will wrap email-sending functionality in a separate job.

Create a new SendEmailVerification job using this command:

php artisan make:job SendEmailVerification

Then update all its contents with the following:

app/Jobs/SendEmailVerification.php

namespace App\Jobs;
 
use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
 
class SendEmailVerification implements ShouldQueue
{
protected $user;
 
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
 
public function __construct(User $user)
{
$this->user = $user;
}
 
public function handle(): void
{
if ($this->user instanceof MustVerifyEmail && ! $this->user->hasVerifiedEmail()) {
$this->user->sendEmailVerificationNotification();
}
}
}

Then update the store method of the RegisteredUserController by adding the SendEmailVerification::dispatch($user); statement to dispatch a job to send email verification.

app/Http/Controllers/Auth/RegisteredUserController.php

use App\Jobs\SendEmailVerification;
 
// ...
 
public function store(Request $request): RedirectResponse
{
// ...
 
event(new Registered($user));
 
SendEmailVerification::dispatch($user);
 
Auth::login($user);
 
return redirect(RouteServiceProvider::HOME);
}

Now when a new user is registered this job will be put into Queue to be processed by Queue Worker. Try it. Email should not be delivered yet this time, because we do not have a queue worker running. If we inspected the jobs table in our database we would see that there's a new entry for this task.

Jobs table

The payload field has all the required data to process this job.

  1. You may run the worker using the queue:work artisan command. It will start a queue worker and process new jobs as they are pushed into the queue.
php artisan queue:work

Once the queue:work command has started, it will continue to run until it is manually stopped or you close your terminal.

Now email will be delivered and the jobs table should be empty. It holds only pending jobs and removes them as soon as the job is complete (email is sent in our case).

Queue Worker options

Worker sleep duration

When jobs are available on the queue, the worker will keep processing jobs with no delay in between jobs. However, the sleep option determines how many seconds a worker will "sleep" if there are no jobs available. Of course, while sleeping, the worker will not process any new jobs:

php artisan queue:work --sleep=3

This is useful when you have an empty queue for longer periods and is more efficient than constantly polling queue driver for new jobs.

Processing jobs for a given number of seconds

The --max-time option may be used to instruct the worker to process jobs in the given number of seconds and then exit. This option may be useful when combined with Supervisor so that your workers are automatically restarted after processing jobs for a given amount of time, releasing any memory they may have accumulated:

php artisan queue:work --max-time=3600

Max attempts

If one of your queued jobs is encountering an error, you likely do not want it to keep retrying indefinitely. Therefore, Laravel provides various ways to specify how many times or for how long a job may be attempted.

One approach to specifying the maximum number of times a job may be attempted is via the --tries switch on the Artisan command line. This will apply to all jobs processed by the worker unless the job being processed specifies the number of times it may be attempted:

php artisan queue:work --tries=3

More options can be seen by issuing this command:

php artisan help queue:work

Parallel processing

To process jobs in parallel you may run multiple queue:work instances. This can be useful to speed up short jobs like sending emails to process them faster.

Queue workers & Deployment

Queue workers store the booted application state in memory. As a result, they will not notice changes in your code base after they have been started. So, during your deployment process, be sure to restart your queue workers, by running the queue:restart command.

php artisan queue:restart

Queue workers & Development

While developing your application you may run the queue:listen command. It is significantly less efficient than the queue:work command because queue:listen boots the application for every iteration, but you don't have to manually restart the worker when you want to reload your updated code or reset the application state.

What's next?

To keep the queue:work process running permanently in the background, we should use a process monitor such as Supervisor to ensure that the queue worker does not stop running. Let's move to another chapter to set up Supervisor for this purpose.

We can now stop the queue:work process and let's move to another chapter to set up Supervisor for this purpose.

avatar

Should that read "your code base after they have been started" instead of "your case base after they have been started" ?

avatar

thanks, fixed

avatar

! $this->user->hasVerifiedEmail() with this case this is not working

avatar

How about giving an use case of something less trivial then sending emails?

avatar

Hello! Is the example supposed to be run on local Ubuntu machine or on AWS EC2 server instance?

avatar

Be carefull with parallel queues, sometimes it can messup order fo jobs if you use the same queue for batch jobs or jobs in general where order matters.

avatar

Please in this code :

   event(new Registered($user));
    SendEmailVerification::dispatch($user);

Is it obligated to dispatch the event Registered before the job can be dispatched ? Are the two related ? Or could I just dispatch de job without firing the event to receive the mail ?

avatar

In short: no, it is not obligated, you can dispatch job immediately. In case you have more jobs on certain event, i.e. when user registers you can add them in EventServiceProvider.