dmeganoski@gmail.com's avatar

Laravel 5.3 - Using Mailable inside a Notification

While reading up on the new features in Laravel 5.3, one of my first thoughts was to question whether the mailable class could be used in conjunction with the notification class. It can! but it took me a while to figure out why it wasn't working as expected, so I'm here to share my thoughts (this may be a bug)

The simplified MailMessage class (as far as I have found) is somewhat limiting. I often have a custom email template editor built into the GUI, and the Mailable class seems like the perfect place to store the logic to check for a custom template in the database before using the default blade template.

What I am doing in this particular case is sending both an email and sms to a user which is stored in a secondary, non-authenticated table. (does not have site access). I use the 'Notifiable' trait on the model (call them customers) and use the $customer->notify(new CustomerNotification) function.

In doing this with the simplified MailMessage class, the 'to' address is automatically resolved, because of the 'email' field on the Notifiable Customer model.

When returning an instance of Mailable instead of MailMessage, however, the address was not automatically resolved, and the mail failed silently. I believe that this is a bug, that it should automatically resolve the address, but I'm not sure.

Since I was already passing an instance of my Customer class to the Mailable constructor, I used $this->to($customer->email, $customer->name) in the build function of the Mailable class to resolve this issue.

Hope this helps someone else.

0 likes
13 replies
datune's avatar

Thank you! It did help me, it was EXACTLY what I was experiencing, and finding this resolved it.

Gerard's avatar

Thanks! I was assuming that a mailable should work via notification and I was scratching my head to find out why it wasn't sending the mail.

andrewc's avatar

Could you show your code in the Notification toMail() method for returning a Mailable class?

dmeganoski@gmail.com's avatar

Sure. Here is a very basic example.

Notification:

class UserRegistered extends Notification {
    use Queueable;
    
    protected $user;
    
    public function __construct(User $user) {
        $this->user = $user;
    }
    
    public function toMail($notifiable) {
        return new \Core\Mail\UserRegistered($this->user);
    }
}

Mailable:

class UserRegistered extends Mailable {
    
    /**
     * @var User
     */
    protected $user;
    
    public function __construct(User $user) {
        $this->user = $user;
    }
    
    /**
     * Build the message.
     *
     * @return $this
     */
    public function build() {
        
        $this->to($this->user->email);
        
        return $this->view('emails.user_registered');
    }
}

Calling it in the controller:

$user->notify(new UserRegistered($user));
1 like
eightyfive's avatar

Hey cool, thanks for that, I ran into the same problem and had to dig into the source code to come out with (approx.) the same conclusion.

Quick note

Passing $user down to your Mailable instance defies the purpose of Notifications.

A better approach is to pass the to param inside the Notification class itself, in the toMail method:

class UserRegistered extends Notification {
    ...

    public function toMail($notifiable)
    {
        $mail = new \Core\Mail\UserRegistered();
        $mail->to($notifiable->email);

        return $mail;
    }
}

7 likes
dmeganoski@gmail.com's avatar

Right, I actually considered adding after that that in most cases, the notifiable variable would be better to use instead.

I pulled this from an example that I had to do some extensive logic in determining which (of multiple) emails and phone numbers to use, in which case I did the logic to determine which email in the Mailable class, but that could still be done in toMail()

stevelacey's avatar

I stumbled here too, and decided to make a custom MailableChannel that implements the behaviour I expected, this allowed my mail notifications to be consistent with others I already have

class MailableChannel extends MailChannel
{
    /**
     * Send the given notification, but unlike MailChannel, set the to address on Mailables
     *
     * @param  mixed  $notifiable
     * @param  \Illuminate\Notifications\Notification  $notification
     * @return void
     */
    public function send($notifiable, Notification $notification)
    {
        if (!$to = $notifiable->routeNotificationFor(self::class, $notification)) {
            return;
        }

        return $notification->toMailable($notifiable)->to($to)->send($this->mailer);
    }
}
Fayçal Borsali's avatar

Hello and thank you for this solution.

I am actually using Laravel 6.7 and the problem was still there and I had to fix it your way !

But I plan to upgrade to Laravel 8.x, has this bug been fixed ? Should I change the code once upgraded ?

Thank you !

Abdel Elrafa's avatar

For anyone still looking for a solution, I found the cleanest way to be:

public function toMail($notifiable)
{
    return (new OrderSubmitted())->to($notifiable->routeNotificationFor('mail', self::class)));
}
andrewc's avatar

After much struggling with the above... I managed to the mail to send by setting the ->to

$mail->to($notifiable);

Please or to participate in this conversation.