# How to chain Laravel mails in two different ways
# Introduction
Laravel makes it simple (opens new window) for you to build and send emails. It also allows you to queue emails. However, even though it uses jobs in the background to queue mails, it doesn't provide us with a simple way to chain mails in the same way we can chain jobs (opens new window) (yet). In this article we'll explore two ways in which we could chain mails, in other words, how we can send a list of mails, one after each other, provided the preceeding mail was sent successfully.
# Our Example
Sometimes we only want to send a mail after we are sure some other mail or mails have been sent successfully. For example, we might have a form on our site that end-users can use to send messages to our site's administrators. In this case we might want to send a confirmation email to the end-user once we have successfully sent their message to the administrators.
Let's say we have two Mailable
(opens new window) classes for the two types of mails we want to send:
ContactFormSubmitted(User $user, ContactForm $data)
: The mail that should be sent to our site's administrators when a user has submitted a message to the contact form. It's constructor takes two parameters:User $user
: the user who submitted the contact form.array $data
: an array that contains the data the user submitted to the contact form.
ContactFormSubmmitedConfirmation
: The mail that should be sent to the user who submitted the contact form to confirm that we have successfully sent their submission to our site's administrators. It's constructor takes the same two parameters as theContactFormSubmitted
's constructor,$user
and$data
.
# Solution 1: Create a new job that sends the mails to be chain synchronously
# The Idea
This is the simplest solution between our two solutions. The idea here is to create a new job class and to send our mails one after the other using the ->send
method inside the handle
method of the job.
Of course, if our mailables implemented the ShouldQueue
contract, they would be queued even if we called the ->send
method on them and thus we would have no idea whether they were sent successfully before sending the next mail. Thus none of our mailables should implement the ShouldQueue
contract for this solution to work.
# Implementation
# Steps
- First we generate a new job called
SendContactFormMails
usingphp artisan make:job SendContactFormMails
- Then we add the data we want to send to our mailables as parameters to the job's constructor. In this case,
User $user
andarray $data
. - We add save the arguments from the constructor in
protected
variables so that we can use them again later in the job'shandle
method. - We add create and send the mails like we normally would, one after the other in the order than we want them to be chained. If one of them fails, the job would fail and the rest of the mails will not be sent.
- Finally, we set the jobs
$tries
value to one. Otherwise the job might be run and some of the mails would be sent more than once.
# Result
use App\User;
use App\Mail\ContactFormSubmmitedConfirmation;
use App\Mail\ContactFormSubmitted;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Mail;
class SendContactFormMails implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $user;
protected $data;
public $tries = 1;
public function __construct(User $user, array $data)
{
$this->user = $user;
$this->data = $data;
}
public function handle()
{
// Send the ContactFormSubmitted mail synchronously
Mail::to(config('mail.site_admin'))
->send(new ContactFormSubmitted($user, $data));
// Send the ContactFormSubmittedConfirmation mail once the
// ContactFormSubmitted mail was successfully sent
Mail::to(data['email'])
->send(new ContactFormSubmmitedConfirmation($user, $data));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# Reflection
This approach is easy to understand and implement. However, it is not an easily reusable solution. Once we have other mails that we want to chain we would have to create separate custom jobs.
# Solution 2: Extend the SendQueuedMailable
job that Laravel creates and chain that job
# Laravel background
For this solution we need to understand a little bit more about how Laravel queues mails in the background.
Laravel's generated mail classes extend from the Illuminate\Mail\Mailable
(opens new window) class.
The Mail
facade uses the Illuminate\Mail\Mailer
(opens new window) to create a new PendingMail
(opens new window) object. When you run queue
on either the Mailer
class or the PendingMail
object it calls the queue
function on the Mailable
object. The queue
method in the Mailable
class creates a new Illuminate\Mail\SendQueuedMailable
(opens new window) job with the Mailable
object as a parameter, and pushes the job to the queue. Finally, when the SendQueuedMailable
job is handled it calls the send
method on the Mailable
class.
# The Idea
The idea for this solution is to make the job that Laravel creates (the SendQueuedMailable
job) "chainable." Then we'll be able to create these jobs ourselves using Mailable
objects and finally, we'll be able to chain those jobs.
# Implementation Step 1: Extend the SendQueuedMailable
class to make it "chainable"
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Mail\SendQueuedMailable;
class SendDispatchableMail extends SendQueuedMailable implements ShouldQueue
{
use Dispatchable, Queueable;
}
2
3
4
5
6
7
8
9
10
11
12
13
# Implementation Step 2: Chain SendDispatchableMail
jobs using Mailable
objects
// Create the ContactFormSubmitted mail (but don't send it yet)
$admin_mailable = new ContactFormSubmitted($user, $data));
$admin_mailable->to(config('mail.site_admin'));
// Create the ContactFormSubmittedConfirmation mail (but don't send it yet)
$user_mailable = new ContactFormSubmittedConfirmation($user, $data));
$user_mailable->to($data['email']);
// Create SendDispatchableMail jobs, chain and dispatch them
SendDispatchableMail::dispatch($admin_mailable)->chain([
new SendDispatchableMail($user_mailable),
]);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Reflection
This approach is difficult to understand and it uses non-standard ways to create and send mails, thus it feels hacky. I also don't like extending and using Laravel classes in ways that it was not intended to be used, because they might create issues when we want to upgrade to a new Laravel release.
The only benefit I see in this solution is that it is reusable. You don't have to create a new custom job for every chain of mails that you want to send like you would have to do when you use the first solution.
Newsletter
If you'd like to subscribe to my blog, please enter your details below. You can unsubscribe at any time.