# 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 the ContactFormSubmitted'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

  1. First we generate a new job called SendContactFormMails using php artisan make:job SendContactFormMails
  2. Then we add the data we want to send to our mailables as parameters to the job's constructor. In this case, User $user and array $data.
  3. We add save the arguments from the constructor in protected variables so that we can use them again later in the job's handle method.
  4. 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.
  5. 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));
    }
}
1
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;
}
1
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),
]);
1
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.

Powered by Buttondown.

Last Updated: 11/20/2023, 10:04:51 AM