★ Sending a welcome notification to new users of a Laravel app
Tags: laravel, php
Published: 2020-01-02 23:42:17

---
title: ★ Sending a welcome notification to new users of a Laravel app
published: true
date: 2019-11-15 09:48:25 UTC
tags: laravel, php
canonical_url: https://freek.dev/1500-sending-a-welcome-notification-to-new-users-of-a-laravel-app
---

[My team](https://spatie.be) and I currently building [Mailcoach](https://mailcoach.app/), a solution to self-host newsletters and email campaigns. In Mailcoach you can create new users to use the app.

How should these new users be onboarded? The easy way out would be to send these new users a default password reset notification to those users, but that isn't a good first experience. The default auth scaffold by Laravel doesn't help us here: it only contains functionality to log in and to let users register themselves.

To onboard new users created by other users, I've created a package called [`laravel-welcome-notification`](https://github.com/spatie/laravel-welcome-notification) which can send a welcome notification to new users that allows them to set an initial password.

In this blogpost I'd like to explain how you can use [the package](https://github.com/spatie/laravel-welcome-notification)).

## Sending a welcome notification #

After [installing](https://github.com/spatie/laravel-welcome-notification#installation) the package, you can send a welcome notification by calling the `sendWelcomeNotification` on a newly created user.

```php
$expiresAt = now()->addDays(3);

$user->sendWelcomeNotification($expiresAt);
```

By default, this method will send a mail to the users with a link to a welcome screen where the user can set an initial passwords. The `sendWelcomeNotification` accepts a `Carbon` instance that determines when that welcome link will be expire.

The link that will be mailed is [a signed url](https://laravel.com/docs/6.x/urls#signed-urls). This will make sure that only urls generated by your app can be used to display and use the welcome screen.

## Customising the mail sent #

By experience I know that a lot of times there will be a need to customise the mail sent to a user.

To customise the mail you should extend `Spatie\WelcomeNotification\WelcomeNotification` provided by the package and override the `buildWelcomeNotificationMessage` method.

```php
class MyCustomWelcomeNotification extends WelcomeNotification
{
    public function buildWelcomeNotificationMessage(): Illuminate\Notifications\Messages\MailMessage
    {
        return (new MailMessage)
            ->subject('Welcome to my app')
            ->action(Lang::get('Set initial password'), $this->showWelcomeFormUrl)
    }
}
```

Next, you must add a method called `sendWelcomeNotification` to your `User` model.

```php
public function sendWelcomeNotification(\Carbon\Carbon $validUntil)
{
    $this->notify(new MyCustomWelcomeNotification($validUntil));
}
```

And with that in place you have total freedom of how the notification should look like.

## Customising the welcome form #

When the user click the welcome link in the sent mail, a welcome form will be displayed. The user can use this form to set an initial password.

To style and customise the form you should publish it

```bash
php artisan vendor:publish --provider="Spatie\WelcomeNotification\WelcomeNotificationServiceProvider" --tag="views"
```

In `resources/vendor/welcome-notification/welcome.blade.php` you now have full access to the html of the form.

## Customising the behaviour #

The installation instructions of the package explain how you should set up `WelcomeController` of your own.

This `WelcomeController` allows you to customise what should happen after a user has set an initial password.

After the a user has successfully set a new password the `sendPasswordSavedResponse` of the `WelcomeController` will get called.

```php
class MyWelcomeController extends BaseWelcomeController
{
    public function sendPasswordSavedResponse()
    {
        // maybe also set a flash message here
    
        return redirect()->route('home');
    }
}
```

If you added extra fields to the welcome form you can add a `rules` function to validate them.

Here's an example where we want to validate an extra field named `job_title`.

```php
class MyWelcomeController extends BaseWelcomeController
{
    public function rules()
    {
        return [
            'password' => 'required|confirmed|min:6',
            'job_title' => 'required',
        ];
    }
}
```

## How it works behind the scenes #

Welcome links should only be allowed to use once. Otherwise those links, which probably have a long expiration period, can be used after the user sets an initial password.

In a first implementation of the package, we [piggy](https://github.com/spatie/laravel-welcome-notification/blob/5cb8531e62ca1d3c13aa5cc96605f056b3ef607f/src/WelcomeNotification.php#L59) [backed](https://github.com/spatie/laravel-welcome-notification/blob/5cb8531e62ca1d3c13aa5cc96605f056b3ef607f/src/WelcomeController.php#L16) on Laravel's password reset tokens. The benefit by doing that was that when a [token is used](https://github.com/spatie/laravel-welcome-notification/blob/5cb8531e62ca1d3c13aa5cc96605f056b3ef607f/src/WelcomeController.php#L29), it will be deleted by Laravel, so the welcome link wouldn't work twice. The big draw back of this approach was that the expiration time of a welcome link would be tied to the expiration time set for password resets.

Password reset links should expire fast. When a user initiates a password reset, he or she will fairly quickly check if the reset mail arrived. An expiration time of an hour or so is enough for most cases. But when new users are created by other users, those new users aren't checking their inboxes. Probably users will click welcome links after more than an hour. In most cases

After [a gentle nudge by Joseph Silber](https://twitter.com/joseph_silber/status/1194244749110185984) we decided [to swap out](https://github.com/spatie/laravel-welcome-notification/commit/5eb45b7537c6ddde51caffae6aceef3edb6faa1d) token based URLs for [signed URLs](https://laravel.com/docs/6.x/urls#signed-urls). The problem we had to solve with signed URLs is that they can't be invalidated.

Luckily the solution is simple. In addition on only relying on the expiration of a signed URL. The package will [save the expiration time](https://github.com/spatie/laravel-welcome-notification/blob/f23351a1ca5e4baa5a321e11d5e18088edf489b3/src/WelcomeNotification.php#L65-L66) in the users table.

```php
protected function initializeNotificationProperties(User $user)
{
    $this->user = $user;

    $this->user->welcome_valid_until = $this->validUntil;
    $this->user->save();

    $this->showWelcomeFormUrl = URL::temporarySignedRoute(
        'welcome', $this->validUntil, ['user' => $user->id]
    );
}
```

Whenever a password is set using the welcome form, [the `welcome_valid_until` field is set to `null`](https://github.com/spatie/laravel-welcome-notification/blob/46ad005bffd8cc5ca564475713e4a19f9563d390/src/WelcomeController.php#L27-L28).

```php
public function savePassword(Request $request, User $user)
{
    $request->validate($this->rules());

    $user->password = bcrypt($request->password);
    $user->welcome_valid_until = null;
    $user->save();

    auth()->login($user);

    return $this->sendPasswordSavedResponse();
}
```

The [middleware that protects the welcome routes](https://github.com/spatie/laravel-welcome-notification/blob/ba0842917d880eb9c760185b78df4eae1a1e39c9/src/WelcomesNewUsers.php) checks if the signed URL is valid and if the `welcome_valid_until` field contains a date that is not in past.

```php
public function handle($request, Closure $next)
{
  if (! $request->hasValidSignature()) {
	abort(Response::HTTP_FORBIDDEN, 'The welcome link does not have a valid signature or is expired.');
  }

  if (! $request->user) {
	abort(Response::HTTP_FORBIDDEN, 'Could not find a user to be welcomed.');
  }

  if (is_null($request->user->welcome_valid_until)) {
	return abort(Response::HTTP_FORBIDDEN, 'The welcome link has already been used.');
  }

  if (Carbon::create($request->user->welcome_valid_until)->isPast()) {
	return abort(Response::HTTP_FORBIDDEN, 'The welcome link has expired.');
  }

  return $next($request);
}
```

With all of this in place our two goals are achieved:

- welcome links can have an expire time separate from password resets
- welcome links can only be used once

## In closing #

Personally I've coded up this logic a couple of times in separate projects (here's an old blogpost about it), so it made sense to package it up. This isn't the first package that my team has created. Check out this [big list of packages](https://spatie.be/open-source/packages) we've created previously.

This package was created to be used in upcoming [Mailcoach](https://mailcoach.app) application and package. Mailcoach will allow you to self host your newsletters and email campaigns. To get updates on the project and get notified when it gets released, sign up to [the email list at mailcoach.app](https://mailcoach.app)