Customise the Laravel User Verification email

php

Starting with Laravel 5.7, the framework has updated the User and Auth models to include an email verification contract.

While this is very straightforward to initially configure, customising the content of the emails sent is not so obvious.

Initial Setup

Create a new Laravel project, which we shall call ‘blog’. Note that this tutorial is based upon Laravel 6.0 but other versions should be very similar or identical.

user@host:~$ composer create-project --prefer-dist laravel/laravel blog

Change to the blog directory and install the provided scaffold.

user@host:~$ cd blog
user@host:~/blog$ composer require laravel/ui
user@host:~/blog$ php artisan ui vue --auth
user@host:~/blog$ npm install && npm run dev

The User model must be updated in order for the verification emails to be fired automatically. Do this now by adding the MustVerifyEmail contract to the model in app/User.php as shown below.

<?php

namespace App;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable implements MustVerifyEmail
{
    use Notifiable;

    ...
}

Setup your .env file for your prefered database, and migrate the user tables.

user@host:~/blog$ php artisan migrate

Finally, alter the definition of Auth::routes() in routes/web.php as shown below to include the required views for verification.

Auth::routes(['verify' => true]);

Now when you register a new user in your app you they will receive the bland generic email shown below which includes a signed URL to allow them to verify receipt of the email and therefore their address.

Default Laravel email validation message

How it Works

The RegisterController (app/Controllers/RegisterController.php) in the app uses traits from RegisterUsers (vendor/laravel/framework/src/Illuminate/Foundation/Auth/RegisterUsers.php) which in turn emits a ‘Registered’ event upon successful user registration. The listeners for the app are defined in app/Providers/EventServiceProvider.php and you will see there is one for the ‘Registered’ event already defined

protected $listen = [
        Registered::class => [
            SendEmailVerificationNotification::class,
        ],
    ];

The SendEmailVerificationNotification class (vendor/laravel/framework/src/Illuminate/Auth/Listeners/SendEmailVerificationNotification.php) once fired calls the SendEmailVerificationNotification() function for your User class. This function is included by the MustVerifyEmail trait we implemented earlier and invokes the VerifyEmail notification.

So the process flow is

        RegisterController                  (Controller)
                | 
           RegisterUsers                    (Trait)
                |
            Registered                      (Event)
                |
        EventServiceProvider                (Service Provider)
                |
SendEmailVerificationNotification           (Listener)
                |
               User                         (Model)
                |
          MustVerifyEmail                   (Trait)
                |
            VerifyEmail                     (Notification)

Now it is very bad practice to edit any code in the vendor directory, but in this instance all we need do is to override the SendEmailVerificationNotification() in your User model. By overriding this function in the immediate scope of the class, it will not call the function inherited from the trait.

Customise

Firstly, let us create the new email notification we wish to send.

user@host:~/blog$ php artisan make:notification UserVerificationEmail --markdown

And edit the class in app/Notifications/UserVerificationEmail.php to look like below. Because the verification controller is expecting a signed URL we need the additional function to build that from the user id and email address to subsequently pass to the email message.

<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\URL;

class UserVerificationEmail extends Notification
{
    use Queueable;

    /**
     * Create a new notification instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return ['mail'];
    }

    /**
     * Get the mail representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return \Illuminate\Notifications\Messages\MailMessage
     */
    public function toMail($notifiable)
    {
        return (new MailMessage)
        ->subject('Please verify your email address')
        ->markdown(
            'emails.userverify', 
            [
               'url' => $this->verificationUrl($notifiable),
               'notifiable' => $notifiable,
            ]
        );
    }

    /**
     * Get the array representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function toArray($notifiable)
    {
        return [
            //
        ];
    }

    /*
   * Build the verification URL
   *
   * @return URL
   */
   protected function verificationUrl($notifiable)
   {
      return URL::temporarySignedRoute(
         'verification.verify',
         Carbon::now()->addMinutes(
            Config::get('auth.verification.expire', 60)),
              [
                'id' => $notifiable->getKey(),
                'hash' => sha1($notifiable->getEmailForVerification()),
              ]     
         ); 
   }
}

Now create the view for the message as resources/views/emails/userverify.blade.php and edit to look as follows.

@component('mail::message')
# Welcome {{ $notifiable->name }}

Before you can use this tutorial system you must verify your email address.

@component('mail::button', ['url' => $url])
Brabeum Verify Email Address Tutorial
@endcomponent

If you did not create an account, no further action is required.
Thanks,

{{ config('app.name') }} Team

@component('mail::subcopy')
If you’re having trouble clicking the "Verify Email Address" button, copy and paste the URL below into your web browser: {{ $url }} 
@endcomponent

@endcomponent

Now add the following use and function to the User model

use App\Mail\UserVerificationEmail;
use Illuminate\Support\Facades\Mail;
...

class User extends Authenticatable implements MustVerifyEmail
{
    use Notifiable;

    ...

   public function sendEmailVerificationNotification()
   {
      $this->notify(new Notifications\UserVerificationEmail);
   }
  
   ...
}

Now if you test it all together, you should see the following email has been sent

New user email

Summary

Although the email notification we have constructed is superficially similar to the one provided with Laravel, we are now in a position to customise it further to suit the needs of our application.