Tangible Bytes

A Web Developer’s Blog

Laravel Reset Password Email With Extra Data

I needed to send password reset emails from Laravel with additional data so that depending on how the password reset is triggered the email content varies.

See also

My related post on Multiple Authentication in Laravel

I have more than one authenticatable model.

This is the method that send a password reset email on my authenticatable


    public function sendResetPasswordLink(Site $site, Request $request)
    {
        $request->validate(['email' => 'required|email']);

        $status = Password::broker('accounts')->sendResetLink(
            $request->only('email'),
            function ($user, $token) use ($site) {
                Mail::to($user->email, "{$user->firstname} {$user->lastname}")->send(new AccountPasswordReset($user, $site, $token));
                return Password::RESET_LINK_SENT;
            },
        );

        return response()->json($status);
    }

I couldn’t see how to do this at first till I dug into the source for PasswordBroker.php

    /**
     * Send a password reset link to a user.
     *
     * @param  array  $credentials
     * @param  \Closure|null  $callback
     * @return string
     */
    public function sendResetLink(#[\SensitiveParameter] array $credentials, ?Closure $callback = null)
    {
        // First we will check to see if we found a user at the given credentials and
        // if we did not we will redirect back to this current URI with a piece of
        // "flash" data in the session to indicate to the developers the errors.
        $user = $this->getUser($credentials);

        if (is_null($user)) {
            return static::INVALID_USER;
        }

        if ($this->tokens->recentlyCreatedToken($user)) {
            return static::RESET_THROTTLED;
        }

        $token = $this->tokens->create($user);

        if ($callback) {
            return $callback($user, $token) ?? static::RESET_LINK_SENT;
        }

        // Once we have the reset token, we are ready to send the message out to this
        // user with a link to reset their password. We will then redirect back to
        // the current URI having nothing set in the session to indicate errors.
        $user->sendPasswordResetNotification($token);

        $this->events?->dispatch(new PasswordResetLinkSent($user));

        return static::RESET_LINK_SENT;
    }

Note that by supplying a callback we bypass the usual notification process and handle it locally.

Create a custom Mailable

artisan make:mail AccountPasswordReset

<?php

namespace App\Mail;

use App\Models\Account;
use App\Models\Site;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;

class AccountPasswordReset extends Mailable
{
    use Queueable, SerializesModels;

   /**
    * Note the parameters
    */
    public function __construct(public Account $user, public Site $site, public string $token)
    {
        //
    }
    public function envelope(): Envelope
    {
        return new Envelope(
            from: new Address('admin@example.com', "My {$this->site->name}"),
            subject: "Set up a new password for {$this->site->name}",
        );
    }
    public function content(): Content
    {
        return new Content(
            view: 'mail.account.resetpassword',
        );
    }
    public function attachments(): array
    {
        return [];
    }
}

Note that I have can defined additional variables in the constructor.

These are passed in when a new instance of this Mailable is created.

They are then available to the Blade template

Email Template

make:view mail.account/resetpassword

The content of this are like any blade template and your variables should be present.


<h1>Hi {{ $user->firstname . " ".$user->lastname }},</h1>
<p>
You recently requested to reset your password for your My {{ $site->name }} account. Use the button below to reset it. 
<strong>This password reset is only valid for the next 1 hour.</strong>
</p>
        

Conclusion

In my case I wanted to specify the $site parameter on the request and pass that through to the email to send a customised message based on the request.

Caveat

This code isn’t live yet - it seems to pass initial testing but let me know if you spot bugs or if you see a better way of doing this.