Unleash the power of Laravel queue and Task Scheduler Part-1

Md. Abul Hassan
8 min readApr 1, 2024

--

Sending notifications email

Alhamdulillah finally I started this series. This series is all about how we can use Laravel queues to solve our real-life problems. This part will be how we can send email via queue. This email can be a new user register email, a user forgets password email, email verification email. In a word how we can send email? Let’s explore.

I have started this series with Laravel 11 and database as a queue driver. Since I used the database as a queue driver that’s why we need migration.

For Laravel 11 run this command for the queue table if you don’t have

php artisan make:queue-table

For Laravel 10 or less use this command

php artisan queue:table

This command will create a migration that looks like this.

Laravel jobs table migration

Run this artisan command for the database migration

php artisan migrate

At this point modify your .env file if you use Laravel 10 or less version. Set this.

QUEUE_CONNECTION=database

Now our basic queue configuration is ready to use to send an email to the user after successful registration.

For this, first of all, I have to create a job class that will handle email-sending logic. Run this artisan command to generate a job class.

php artisan make:job UserEmailSendingJob

It will create a job class in the app/Jobs directory, and it looks like this.

UserEmailSendingJob

Keep in mind job class should implement the ShouldQueue interface to communicate with the queue.

I have used Laravel Breeze to make my job easy and Mail Trap for email testing. Go to Mail trap and make an inbox and in the SMTP setting section select Laravel and copy this configuration to your .env file.

Mail trap configuration.

Now our testing mail is configured successfully. Now we have to dispatch our EmailSendingJob when user registration is successful. For this navigate app/Http/Controllers/Auth/RegisteredUserController.php and it looks like this.

Laravel Breeze Register Controller

In here after successful registration, we will dispatch our EmailSendingJob with the dispatch() method. It will be like this

UserEmailSendingJob dispatch

I have dispatched UserEmailSendingJob after successful registration and passed user info to the job class. I have imported the App\Jobs\UserEmailSendingJob namespace on top of the registration controller. Now when a user registers them successfully one job will be dispatched to the queue for email sending. Now we can register a dummy user to check if the job is dispatched or not. Let’s hit the registration route and Breeze’s default registration page look like this

Breeze default register page

I have used fake filler to fill up this form. After one successful user registration now we will look up our user’s table and jobs table if our job was dispatched or not. This is our user’s table.

Users table

and this is our jobs table

Jobs table

So, our job is successfully dispatched to the queue. Now we need to start to run our queue worker. To start a queue worker within your project directory run this artisan command.

php artisan queue:work

You also can run this artisan command to run queue worker.

php artisan queue:listen

But if you run queue: work it will create a single instance of application. It has better memory management. Since queue: work creates a single instance of the application that’s why if you change something in your job class this will not apply to queue: work command directly. You have to stop the queue worker and then start again.

On the other hand, queue: listen command creates a separate application for every single queue. That’s why if you change something within your job class it will apply instantly. And queue: work command consumes memory heavily that’s why it’s not recommended for production. Oke now will run the queue: work command.

It’s running and done but our mail is not sent. Because we don’t configure mail within our UserEmailSendingJob class. Now we will configure simple raw mail. At first, we need to catch user info via the UserEmailSendingJob class constructor. This data is passed from here.

UserEmailSendingJob::dispatch($user);

and our UserEmailSendingJob class constructor looks like

 public function __construct(public User $user)
{

}

we have used PHP 8 property promotion here. Now we have all the information about a user like a name, email, password everything from the user’s table and it is an instance of the User model that’s type hint with User class. Now we have sent mail with the handle method of the UserEmailSendingJob class, and its handle method looks like this.

 public function handle(): void
{
Mail::raw('This is a test raw email.', function ($message) {
$message->to($this->user->email)
->subject('Welcome to Our Website');
});
}

I am not focused on the mail template here that’s why I have created a raw mail and passed a callback function with a parameter to process our operation.

If you are confused about the callback function, anonymous function, arrow function, and closure you can follow this article from me. Hope that it will help to understand this confusing topic.

It’s time to register a new user to check if the job was dispatched successfully or not or email was successfully sent or not.

Now this is our users table.

We can see our user id is 4. Look at jobs table.

The job is successfully dispatched. Now run queue worker to send an email to the user. I am using the gitbash terminal on my Windows machine.

Queue completed our job duty. Now we will check the mail trap inbox if the user get an email or not.

Our user gets a welcome email. This we can handle forgot password email sending.

Customize this job and handle different problems.

The job can fail because of mail service is unavailable or the mail service unauthenticated. We can add several tries to our job. It will tell the queue worker how often this job will be executed.

public $tries = 3;

If we want to, we can specify tries in our queue worker like this.

php artisan queue:work --tries=3

but it has a disadvantage. Imagine you have 20 jobs in your queue and all 20 jobs are not required to retry 3 times if failed that’s why $tries property is the recommended way for controlling job retries.

Okay let’s go forward we have controlled our retry policy, but we did not specify the retry timespan. We can do it with $backoff property. It can be an array or a single value.

public $backoff = 3; or
public $backoff = [5,10]

This one public $backoff = [5,10] is called exponential backoff. $backoff = 3 indicates that retry our failed job after every 3 seconds. $backoff = [3,5] indicates that if our job fails the first time then it will wait for 5 seconds for the second time. If the job fails a second time it will wait 10 seconds for the third time. If we increase our $tries value and the exponential $backoff value remains the same, then it will wait 5 seconds for the second time, and third or more than the third it will wait 10 seconds to retry.

Now this job can throw some exceptions like UnexpectedResponseException. We can handle exceptions in our code, and it will be like this.

  public function handle(): void
{
try {
Mail::raw('This is a test raw email.', function ($message) {
$message->to($this->user->email)
->subject('Welcome to Our Website');
});
} catch (UnexpectedResponseException $e) {
Log::error($e->getMessage());
}
}

Here I have handled exceptions and stored exception messages in the application log. If we follow this our job will be successful and store our error message in the log.

But we can also control how many exceptions we accept with $maxException property. If we do like this.

$maxExceptions = 2

It will allow 2 times exception we allowed for this job. If more than 2 exceptions occur this job will count as a failed and we need to debug this manually. Now our final code looks like

<?php

namespace App\Jobs;

use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Support\Facades\Mail;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;

class UserEmailSendingJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 3;
public $backoff = [5,10];
public $maxExceptions = 2;
/**
* Create a new job instance.
*/
public function __construct(public User $user)
{

}

/**
* Execute the job.
*/
public function handle(): void
{
Mail::raw('This is a test raw email.', function ($message) {
$message->to($this->user->email)
->subject('Welcome to Our Website');
});
}
}

And this is where we dispatch our job.

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Jobs\UserEmailSendingJob;
use App\Models\User;
use Illuminate\Auth\Events\Registered;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules;
use Illuminate\View\View;

class RegisteredUserController extends Controller
{
/**
* Display the registration view.
*/
public function create(): View
{
return view('auth.register');
}

/**
* Handle an incoming registration request.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request): RedirectResponse
{
$request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class],
'password' => ['required', 'confirmed', Rules\Password::defaults()],
]);

$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
UserEmailSendingJob::dispatch($user);
event(new Registered($user));

Auth::login($user);

return redirect(route('dashboard', absolute: false));
}
}

That’s all for this part. If you have any confusion about this article, put a comment to the comment box.

--

--