Every two weeks I send out a newsletter containing lots of interesting stuff for the modern PHP developer. You can expect quick tips, links to interesting tutorials, opinions and packages. Want to learn the cool stuff? Then sign up now!

Sending a welcome mail with Laravel 5.3

Recently I was working an a project where, in order to use the webapp, users should first apply for an account. Potential users can fill in request form. After the request is approved by an admin they may use the app.

Our client expected that the barrier to request an account should be very low. That’s why the request form doesn’t contain a password field. Instead, when an account is approved, a welcome mail is sent with a link to where the user can specify a password.

In this post I’d like to show you how we solved this with Laravel 5.3’s mailables.

High level

This is what we’re going to do. Whenever an admin approves a user we’re going to fire off a UserApproved event. We’re going to listen for that event and, when we hear it, we’ll generate a password reset token and send a welcome mail. The welcome mail contains a link to a screen where a user can choose a password.

Show me the code!

The user model contains a method to approve the user. Whenever a user is approved the UserApproved event is sent.

public function approve(): User
    $this->status = 'approved'

    event(new UserApproved($this));

    return $this;

You could opt to, instead of firing of an event, just send a mail directly from your controller. That’ll work, and for smallish projects that’s perfectly fine in my book. But I prefer, when the application should send out a couple of different mails, to fire off events. We can than listen for those events in a specialized EventHandler. The code of that event handler shows which mails are being sent of when.

Here’s the code of the event handler used in our project:

namespace App\Mail; use App\Events\UserApproved; use App\Events\UserRefused; use App\Events\UserRegistered; use Illuminate\Contracts\Events\Dispatcher; use Mail; class EventHandler { public function subscribe(Dispatcher $events) { $events->listen(UserRegistered::class, function (UserRegistered $event) { Mail::send(new RegistrationReceived($event->user)); Mail::send(new UserWaitingForApproval($event->user)); }); $events->listen(UserApproved::class, function(UserApproved $event) { Mail::send(new Welcome($event->user)); }); $events->listen(UserRefused::class, function(UserRefused $event) { Mail::send(new Refusal($event->user)); }); } }

In this EventHandler we can clearly see which mails are being sent when. If you like this approach and would like to use it as well, don’t forget to register the EventHandler in the EventServiceProvider.

namespace App\Providers;

use Illuminate\Foundation\Support\Providers\EventServiceProvider as IlluminateEventServiceProvider;

class EventServiceProvider extends IlluminateEventServiceProvider
    protected $listen = [];

    protected $subscribe = [

Let’s take a closer look at the App\Mail\Welcome-class. It’s a mailable: a class responsible for configuring and sending a mail message. Head over to the Laravel docs on mailables to learn more.

This welcome mail should contain a link to a screen where a user can pick a password. That functionality reminds me very much of a password reset. A password reset mail also contains a link to such a screen.

A mailable is, in my opinion, the perfect place to put some extra code that should be executed when the mail is going to be sent. In our case we can manually generate a reset token. This is the code to do that:


If we put that line inside our mailable a reset token will be generated when the mail is sent. Here’s the full code of App\Mail\Welcome:

namespace App\Mail;

use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
use Password;

class Welcome extends Mailable implements ShouldQueue
    use Queueable, SerializesModels;

    /** @var \App\Models\User */
    public $user;

    /** @var string */
    public $token;

     * @param \App\Models\User $user
    public function __construct(User $user)
        $this->user = $user;

        $this->token = Password::getRepository()->create($user);

     * Build the message.
     * @return $this
    public function build()
        return $this
            ->subject('Welcome to '.config('app.name'))

Any public property on the mailable is going to be accessible by the view. So the view mails.member.welcome has access to the $user and the newly generated $token

Let’s take a look at the mail-view. config('auth.passwords.users.expire') determines when password reset token will expire. In a vanilla Laravel app is set to 60 minutes. In my project I’ve set this to a higher value.

This is entire contents of that mail.member.welcome view:


    <h1>Welcome to <a href="{{ config('app.url') }}">{{ config('app.name') }}</a></h1>
        Dear {{ $user->first_name }},
        Your account has been approved. You can now pick a password at our site and login.
                    <a href="{{ action([email protected]', [$token]) }}" class="btn-primary">
                       Pick a password

    <p><em>This link is valid until {{ Carbon\Carbon::now()->addMinutes(config('auth.passwords.users.expire'))->format('Y/m/d') }}.</em></p>

The App\Http\Controllers\WelcomeController will handle the click on the link in the sent welcome mail. If the link is valid it will display a form where the user can pick a password. When that valid form is submitted, the password will be saved, the user will be logged in an redirected to the member section.

These are the routes that have been set up for the WelcomeController.

Route::group(['middleware' => 'guest'], function() {
    Route::get('welcome/{token}', [email protected]');
    Route::post('welcome/save-password', [email protected]');

This is the code of the controller itself. It uses the ResetsPasswords trait provided by Laravel. It contains many methods that make it easy to reset a password. Notice that I’ve wrapped my own savePassword method around the reset method. My own method does nothing extra but I thought savePassword is more clear than the generic reset in this context.

namespace App\Http\Controllers;

use Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\Request;
use Password;

class WelcomeController extends Controller
    use ResetsPasswords;

    public function index(Request $request, string $token = null)
        if (! $user = User::findByToken($token)) {
            flash()->error('The link you clicked is invalid.');

            return redirect()->to('/login');

        return view('welcome')->with([
            'token' => $token,
            'email' => $request->email,
            'user' => $user

    public function savePassword(Request $request)
        return $this->reset($request);

     * Get the response for a successful password reset.
    protected function sendResetResponse(string $response): Response
        flash()->info('Welcome! You are now logged in! Your password was saved.');

        return redirect('/member-home');

This is the findByToken method that’s defined on the User model:

 * @param string $token
 * @return \App\Services\Auth\User|null
public static function findByToken(string $token)
    $resetRecord = app('db')->table('password_resets')->where('token', $token)->first();

    if (empty($resetRecord)) {

    return static::where('email', $resetRecord->email)->first();

To finish things off here’s the code for the welcome-view.


@section('title', 'welcome')



    {!! Form::open(['action' => [email protected]']) !!}

    {!! Form::hidden('token', $token) !!}
    {!! Form::hidden('email', $user->email) !!}

       {!! Form::label('password', 'Password', ['class' => 'label--required'] ) !!}
       {!! Form::password('password', null, ['autofocus' ]) !!}

       {!! Form::label('password_confirmation', 'Confirm password', ['class' => 'label--required']) !!}
       {!! Form::password('password_confirmation', [null]) !!}
       {!! Html::error($errors->first('password')) !!}

       {!! Form::button('Save password', ['type'=>'submit']) !!}

    {!! Form::close() !!}


Though there’s quite some code involved in making all of this work, it’s fairly easy to set up. If you have any questions, or suggestions for improving this workflow, let me know in the comments below.

Freek Van der Herten is a partner and developer at Spatie, an Antwerp based company that specializes in creating web apps with Laravel. After hours he writes about modern PHP and Laravel on this blog. When not coding he’s probably rehearsing with his kraut rock band.
  • Juukie14

    Love it! It might be better to calculate the token expiration date based on the config file. I don’t see where 7 days came from 🤔.

  • Aly

    It ia so useful article, thank you, today will be practiced)


  • Shamil Choudhury

    Hi. I’m getting the following error on the WelcomeController :

    Call to undefined method IlluminateDatabaseQueryBuilder::findByToken()

    Is there any workaround to get the user from the token ?

  • Jakub Kratina

    Like for the EventHandler 🙂

  • Lars

    Nice tutorial with a lot of clever use of new laravel features! Hope I can see more real life examples here 🙂