Currently Available: Need a skilled Software Developer for your next project?
Categories
Laravel

Customizing Laravel’s Default Notification Emails

When you build a Laravel application with authentication, Laravel automatically sends several emails to your users - like verification emails when they register or password reset emails when they forget their password. Out of the box, these emails use Laravel's default templates and wording. I'll show you how to customize these emails to match your application's branding and needs.

Laravel includes three main types of default emails:

  • Email Verification emails (sent when users register)
  • Password Reset emails (sent when users request a password reset)
  • Team Invitation emails (sent when using Laravel Jetstream with teams)

Understanding How Laravel Sends Emails

Before we dive into customization, let's understand how Laravel handles sending these emails.

The Laravel Notification System

Laravel uses its notification system to send emails. Think of notifications as standardized messages that can be sent through different channels (email, SMS, Slack, etc.). When Laravel needs to send an email verification, it:

  1. Creates a notification (a PHP class that contains the email's content and logic)
  2. Uses an email template (a Blade view that defines how the email looks)
  3. Combines them to generate and send the actual email

Here's a diagram of how it works:

User Registers → Laravel creates VerifyEmail notification → Notification uses email template → Email is sent

For example, when a user registers, Laravel automatically:

$user->notify(new VerifyEmail);  // This is happening behind the scenes

The Three Pieces You Can Customize

  1. The Notification Class: Controls what's in your email (subject, text, buttons)

    // This defines what goes into the email
    class VerifyEmail
    {
    public function toMail($user)
    {
        return (new MailMessage)
            ->subject('Please verify your email')
            ->line('Click the button to verify')
            ->action('Verify Email', $url);
    }
    }
  2. The Email Template: Controls how your email looks (layout, styling)

    {{-- This defines how the email looks --}}
    <div class="email-template">
    <h1>{{ $subject }}</h1>
    <p>{{ $line }}</p>
    <button>{{ $action }}</button>
    </div>
  3. The Custom Layout: A completely custom email design (optional)

    
    {{-- This is your own custom email design from scratch --}}
    @extends('emails.custom-layout')

@section('content')
{{-- Your completely custom email design --}}
@endsection


Let's learn how to customize each type of email...

## Customizing Email Verification

### Step 1: Create Your Custom Notification

First, create a new notification class:

```bash
php artisan make:notification CustomVerifyEmail

This creates app/Notifications/CustomVerifyEmail.php. Here's how to customize it:

<?php

namespace App\Notifications;

use Illuminate\Auth\Notifications\VerifyEmail as BaseVerifyEmail;
use Illuminate\Notifications\Messages\MailMessage;

class CustomVerifyEmail extends BaseVerifyEmail
{
    public function toMail($notifiable)
    {
        $url = $this->verificationUrl($notifiable);

        return (new MailMessage)
            ->subject('Welcome! Please verify your email')
            ->greeting("Hi {$notifiable->name}!")
            ->line('Welcome to ' . config('app.name') . '! We\'re excited to have you join us.')
            ->line('Please verify your email address by clicking the button below.')
            ->action('Verify My Email', $url)
            ->line('If you did not create an account, no further action is required.');
    }
}

Step 2: Tell Laravel to Use Your Custom Notification

Laravel needs to know that it should use your custom notification instead of its default one. You do this in your User model:

<?php

namespace App\Models;

use App\Notifications\CustomVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    public function sendEmailVerificationNotification()
    {
        $this->notify(new CustomVerifyEmail);
    }
}

Now whenever Laravel would normally send its default verification email, it will use your custom one instead.

Customizing Password Reset Emails

Step 1: Create Your Custom Notification

php artisan make:notification CustomResetPassword

Then customize the notification:

<?php

namespace App\Notifications;

use Illuminate\Auth\Notifications\ResetPassword as BaseResetPassword;
use Illuminate\Notifications\Messages\MailMessage;

class CustomResetPassword extends BaseResetPassword
{
    public function toMail($notifiable)
    {
        return (new MailMessage)
            ->subject('Reset Your Password')
            ->greeting("Hi there!")
            ->line('Forgot your password? No problem! Click the button below to reset it.')
            ->action('Reset Password', url(route('password.reset', [
                'token' => $this->token,
                'email' => $notifiable->getEmailForPasswordReset(),
            ], false)))
            ->line('This password reset link will expire in :count minutes.', [
                'count' => config('auth.passwords.'.config('auth.defaults.passwords').'.expire')
            ])
            ->line('If you didn\'t request this, you can safely ignore this email.');
    }
}

Step 2: Register Your Custom Notification

Again, tell Laravel to use your custom notification in your User model:

<?php

namespace App\Models;

class User extends Authenticatable
{
    public function sendPasswordResetNotification($token)
    {
        $this->notify(new CustomResetPassword($token));
    }
}

Customizing Team Invitation Emails (Jetstream)

Step 1: Create Your Custom Notification

php artisan make:notification CustomTeamInvitation

Customize the notification:

<?php

namespace App\Notifications;

use Laravel\Jetstream\Notifications\TeamInvitation as BaseTeamInvitation;
use Illuminate\Notifications\Messages\MailMessage;

class CustomTeamInvitation extends BaseTeamInvitation
{
    public function toMail($notifiable)
    {
        return (new MailMessage)
            ->subject("You're Invited to Join a Team!")
            ->greeting("Hello!")
            ->line("You've been invited to join the {$this->team->name} team!")
            ->line("You'll be joining as: {$this->invitation->role}")
            ->action('Accept Invitation', $this->acceptUrl)
            ->line('This invitation will expire soon, so accept it while you can!')
            ->line('If you weren\'t expecting this invitation, you can ignore this email.');
    }
}

Step 2: Register Your Custom Notification

For team invitations, you register your custom notification in the JetstreamServiceProvider:

<?php

namespace App\Providers;

use Laravel\Jetstream\Jetstream;
use App\Notifications\CustomTeamInvitation;
use Laravel\Jetstream\JetstreamServiceProvider as ServiceProvider;

class JetstreamServiceProvider extends ServiceProvider
{
    public function boot()
    {
        parent::boot();

        Jetstream::useTeamInvitationModel(CustomTeamInvitation::class);
    }
}

Customizing How Your Emails Look

Now that we've customized what our emails say, let's customize how they look. There are two approaches:

Approach 1: Customize Laravel's Default Email Template

Laravel's notification emails use a default template. You can customize this template to affect all notification emails at once:

  1. First, publish the template to your application:

    php artisan vendor:publish --tag=laravel-notifications
  2. This creates resources/views/vendor/notifications/email.blade.php. Edit this file to customize the look:

<x-mail::message>
{{-- Add your logo --}}
<div style="text-align: center; margin-bottom: 30px;">
    <img src="{{ asset('images/logo.png') }}" alt="{{ config('app.name') }}" style="max-width: 200px;">
</div>

{{-- Customize the greeting --}}
@if (! empty($greeting))
<h1 style="color: #2d3748; font-size: 24px; font-weight: bold; margin-bottom: 20px;">
    {{ $greeting }}
</h1>
@endif

{{-- Style the content --}}
@foreach ($lines as $line)
<p style="color: #4a5568; font-size: 16px; line-height: 1.6; margin-bottom: 15px;">
    {{ $line }}
</p>
@endforeach

{{-- Style the button --}}
@isset($actionText)
<div style="text-align: center; margin: 30px 0;">
    <a href="{{ $actionUrl }}"
       class="button"
       style="background-color: #4f46e5; color: white; padding: 12px 24px; 
              text-decoration: none; border-radius: 5px; font-weight: bold;">
        {{ $actionText }}
    </a>
</div>
@endisset

{{-- Add a nice footer --}}
<div style="color: #718096; font-size: 14px; margin-top: 30px; padding-top: 20px; border-top: 1px solid #e2e8f0;">
    © {{ date('Y') }} {{ config('app.name') }}. All rights reserved.
</div>
</x-mail::message>

Approach 2: Create Your Own Custom Email Layout

If you want complete control over your email design, you can create your own layout from scratch:

  1. Create a layout file:
{{-- resources/views/emails/layouts/custom.blade.php --}}
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
        /* Your custom email CSS */
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            line-height: 1.5;
            margin: 0;
            padding: 0;
            background-color: #f3f4f6;
        }
        .container {
            max-width: 600px;
            margin: 0 auto;
            padding: 20px;
        }
        .card {
            background: white;
            border-radius: 8px;
            padding: 30px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
        .button {
            display: inline-block;
            padding: 12px 24px;
            background-color: #4f46e5;
            color: white;
            text-decoration: none;
            border-radius: 5px;
            font-weight: bold;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="card">
            {{-- Logo --}}
            <div style="text-align: center; margin-bottom: 30px;">
                <img src="{{ asset('images/logo.png') }}" alt="{{ config('app.name') }}" style="max-width: 200px;">
            </div>

            {{-- Content --}}
            @yield('content')

            {{-- Footer --}}
            <div style="margin-top: 30px; padding-top: 20px; border-top: 1px solid #e5e7eb; text-align: center; color: #6b7280;">
                © {{ date('Y') }} {{ config('app.name') }}. All rights reserved.
            </div>
        </div>
    </div>
</body>
</html>
  1. Create specific email templates using your layout:
{{-- resources/views/emails/verify-email.blade.php --}}
@extends('emails.layouts.custom')

@section('content')
    <h1 style="color: #1f2937; font-size: 24px; margin-bottom: 20px;">Verify Your Email</h1>

    <p style="color: #4b5563; margin-bottom: 20px;">
        Thanks for signing up! Please verify your email address to get started.
    </p>

    <div style="text-align: center; margin: 30px 0;">
        <a href="{{ $url }}" class="button">
            Verify Email Address
        </a>
    </div>

    <p style="color: #6b7280; font-size: 14px;">
        If you did not create an account, no further action is required.
    </p>
@endsection
  1. Use your custom template in your notification:
public function toMail($notifiable)
{
    $url = $this->verificationUrl($notifiable);

    return (new MailMessage)
        ->view('emails.verify-email', ['url' => $url]);
}

Remember

  1. Notification classes control what your emails say
  2. Email templates control how your emails look
  3. Custom layouts give you complete design freedom

Troubleshooting Common Issues

Images Not Showing in Emails

Make sure your image paths are absolute URLs. Instead of:

<img src="{{ asset('images/logo.png') }}">

Use:

<img src="{{ url('images/logo.png') }}">

Styling Not Working

Email clients are picky about CSS. Always use inline styles instead of style tags:

<div style="color: blue;"> {{-- Good --}}
<div class="blue-text">     {{-- Might not work --}}

Custom Layout Not Loading

Make sure you're returning a view in your notification:

return (new MailMessage)->view('emails.custom', ['data' => $data]);

Instead of:

return (new MailMessage)->line('Hello')->action(...);
What I'm building

Delegate tasks. Get software.

Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.

Take a look at vroni.com

Subscribe to my newsletter

Get new posts when I publish them.

I respect your privacy. Unsubscribe at any time.

Leave a Reply

Your email address will not be published. Required fields are marked *