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.
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
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.