Laravel Email Verification During User Registration

Posted on

How to Create Email Verification During Registration Using Laravel?

Introduction

If you’ve ever signed up for a website and received that “Please verify your email” message, you’ve experienced email verification in action. It’s not just a nice-to-have feature—it’s a critical security measure that ensures your users are who they say they are.

In this comprehensive guide, I’ll show you exactly how to implement email verification in Laravel 11 (though this works for Laravel 8+). We’ll start from scratch and build a complete, production-ready email verification system.

Why Email Verification Matters?

Before we dive into the code, let’s talk about why you should care about email verification:

  1. Prevents Fake Accounts – Users can’t register with fake or mistyped email addresses
  2. Reduces Spam – Bots have a harder time creating multiple accounts
  3. Ensures Communication – You can actually reach your users via email
  4. Improves Security – Confirms that the email owner authorized the registration
  5. Better Data Quality – Your user database contains valid, reachable email addresses

Now that we understand the “why,” let’s get into the “how.”

Prerequisites

Before we begin, make sure you have:

  • Laravel 11 installed (or Laravel 8+)
  • Basic understanding of Laravel routes and controllers
  • A code editor (VS Code, PhpStorm, etc.)
  • Access to your terminal/command line

That’s it! Let’s get started.

Step 1: Enable Email Verification on Your User Model

Laravel makes email verification incredibly easy with its built-in MustVerifyEmail interface. Here’s how to enable it:

Open your app/Models/User.php file and update it like this:

<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable implements MustVerifyEmail
{
    use HasFactory, Notifiable;

    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    protected $hidden = [
        'password',
        'remember_token',
    ];

    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'password' => 'hashed',
        ];
    }
}

What changed?

  1. Added implements MustVerifyEmail after the class declaration
  2. Made sure email_verified_at is cast as a datetime

That’s it! Your User model now supports email verification.

Step 2: Update Your Registration Controller

Next, we need to make sure the verification email is sent when a user registers. Open app/Http/Controllers/Auth/RegisteredUserController.php and update the store method:

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Auth\Events\Registered;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules;

class RegisteredUserController extends Controller
{
    public function store(Request $request): RedirectResponse
    {
        $request->validate([
            'name' => 'required|string|max:255',
            'email' => 'required|string|lowercase|email|max:255|unique:users',
            'password' => ['required', 'confirmed', Rules\Password::defaults()],
        ]);

        $user = User::create([
            'name' => $request->name,
            'email' => $request->email,
            'password' => Hash::make($request->password),
        ]);

        // This event triggers the verification email!
        event(new Registered($user));

        // Log the user in so they can access the verification notice page
        Auth::login($user);

        // Redirect to the verification notice page
        return redirect()->route('verification.notice');
    }
}

Key points:

  1. event(new Registered($user)) – This line triggers Laravel to send the verification email automatically
  2. Auth::login($user) – We log the user in immediately (but they still can’t access protected pages until verified)
  3. redirect()->route('verification.notice') – Sends them to a page telling them to check their email

Step 3: Protect Your Routes with Email Verification

Now we need to ensure that unverified users can’t access protected areas of your application. Open routes/web.php and add the verified middleware:

<?php

use App\Http\Controllers\DashboardController;
use App\Http\Controllers\ProfileController;
use Illuminate\Support\Facades\Route;

// Public routes
Route::get('/', function () {
    return view('welcome');
});

// Routes that require email verification
Route::middleware(['auth', 'verified'])->group(function () {
    Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');

    Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
    Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
    Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');

    // Add all your protected routes here
});

require __DIR__.'/auth.php';

What’s happening here?

The verified middleware checks if the user’s email_verified_at field is set. If not, they get redirected to the verification notice page. Simple as that!

Step 4: Create a Beautiful Verification Notice Page

When users register, they need clear instructions on what to do next. Let’s create a user-friendly verification page.

If you’re using Inertia.js (Vue/React), create resources/js/Pages/Auth/VerifyEmail.vue:

<script setup>
import { computed } from 'vue';
import GuestLayout from '@/Layouts/GuestLayout.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import { Head, Link, useForm } from '@inertiajs/vue3';

const props = defineProps({
    status: String,
});

const form = useForm({});

const submit = () => {
    form.post(route('verification.send'));
};

const verificationLinkSent = computed(
    () => props.status === 'verification-link-sent'
);
</script>

<template>
    <GuestLayout>
        <Head title="Email Verification" />

        <!-- Success Icon -->
        <div class="mb-6 flex justify-center">
            <div class="rounded-full bg-green-100 p-3">
                <svg class="h-16 w-16 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
                          d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z">
                    </path>
                </svg>
            </div>
        </div>

        <!-- Main Message -->
        <div class="mb-6 text-center">
            <h2 class="mb-2 text-2xl font-bold text-gray-900">
                Verify Your Email Address
            </h2>
            <p class="text-lg text-gray-700">
                Registration successful!
            </p>
        </div>

        <!-- Instructions -->
        <div class="mb-6 rounded-lg bg-blue-50 p-4 text-sm text-blue-800">
            <p class="mb-2 font-semibold">Next steps:</p>
            <ol class="ml-4 list-decimal space-y-1">
                <li>Check your email inbox (<strong>{{ $page.props.auth.user?.email }}</strong>)</li>
                <li>Click the verification link we sent you</li>
                <li>After verification, you can access your dashboard</li>
            </ol>
        </div>

        <!-- Resend button and logout -->
        <form @submit.prevent="submit">
            <PrimaryButton class="w-full justify-center">
                {{ form.processing ? 'Sending...' : 'Resend Verification Email' }}
            </PrimaryButton>
        </form>
    </GuestLayout>
</template>

If you’re using Blade templates, create resources/views/auth/verify-email.blade.php:

<x-guest-layout>
    <div class="mb-4 text-sm text-gray-600">
        {{ __('Thanks for signing up! Before getting started, could you verify your email address by clicking on the link we just emailed to you? If you didn\'t receive the email, we will gladly send you another.') }}
    </div>

    @if (session('status') == 'verification-link-sent')
        <div class="mb-4 font-medium text-sm text-green-600">
            {{ __('A new verification link has been sent to the email address you provided during registration.') }}
        </div>
    @endif

    <div class="mt-4 flex items-center justify-between">
        <form method="POST" action="{{ route('verification.send') }}">
            @csrf

            <div>
                <x-primary-button>
                    {{ __('Resend Verification Email') }}
                </x-primary-button>
            </div>
        </form>

        <form method="POST" action="{{ route('logout') }}">
            @csrf

            <button type="submit" class="underline text-sm text-gray-600 hover:text-gray-900">
                {{ __('Log Out') }}
            </button>
        </form>
    </div>
</x-guest-layout>

Step 5: Configure Email Sending

Now comes the important part: actually sending the emails! Laravel supports multiple email drivers. Let’s look at the most common options:

Option A: Mailtrap (Recommended for Development)

Mailtrap is a fake SMTP server perfect for testing. Emails are caught and displayed in a web interface.

  1. Sign up at https://mailtrap.io (free)
  2. Get your credentials from the demo inbox
  3. Update your .env:
MAIL_MAILER=smtp
MAIL_HOST=sandbox.smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=your_mailtrap_username
MAIL_PASSWORD=your_mailtrap_password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS="noreply@yourapp.com"
MAIL_FROM_NAME="Your App Name"

Option B: Gmail SMTP (Quick Testing)

Want to send real emails for testing? Gmail works great!

Steps:

  1. Enable 2-Factor Authentication on your Gmail account
  2. Generate an App Password:
    • Go to Google Account → Security → 2-Step Verification
    • Scroll down to “App passwords”
    • Generate a password for “Mail”
    • Copy the 16-digit password
  3. Update your .env:
MAIL_MAILER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=youremail@gmail.com
MAIL_PASSWORD=your_16_digit_app_password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS="noreply@yourapp.com"
MAIL_FROM_NAME="Your App Name"

Important: Gmail has a limit of 500 emails per day. Don’t use this for production!

Option C: SendGrid (Production Recommended)

For production applications, use a professional email service like SendGrid.

  1. Sign up at https://sendgrid.com
    • Free tier: 100 emails/day
    • Paid: Starting at $19.95/month for 50,000 emails
  2. Verify your sender identity (domain or single sender)
  3. Create an API Key
  4. Update your .env:
MAIL_MAILER=smtp
MAIL_HOST=smtp.sendgrid.net
MAIL_PORT=587
MAIL_USERNAME=apikey
MAIL_PASSWORD=your_sendgrid_api_key
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS="noreply@yourapp.com"
MAIL_FROM_NAME="Your App Name"

After updating .env, always clear the config cache:

php artisan config:clear

Step 6: Test Your Email Verification

Time to test everything! Here’s the complete flow:

1. Register a New User

# Start your Laravel server if not running
php artisan serve

Visit http://127.0.0.1:8000/register and create a new account.

2. Check for the Verification Email

Depending on your mail driver:

  • Mailtrap: Check your Mailtrap inbox
  • Gmail: Check your Gmail inbox
  • SendGrid: Check your email or SendGrid activity dashboard

3. Click the Verification Link

The email contains a link like:

http://127.0.0.1:8000/verify-email/{id}/{hash}?expires={timestamp}&signature={signature}

Click it, and you should see a success message!

4. Try Accessing Protected Routes

Now try accessing your dashboard. You should be able to access it without being redirected to the verification notice.

Troubleshooting Common Issues

Issue 1: Email Not Sending

Problem: No email is being sent, and no errors appear.

Solutions:

# 1. Clear all caches
php artisan config:clear
php artisan cache:clear
php artisan view:clear

# 2. Check your .env file has correct values
cat .env | grep MAIL

# 3. Test email sending manually
php artisan tinker
Mail::raw('Test email', function ($message) {
    $message->to('test@example.com')->subject('Test');
});

Issue 2: SMTP Authentication Failed

Problem: Error: “SMTP Authentication failed”

Solutions:

  • Double-check your username and password in .env
  • For Gmail: Make sure you’re using an App Password (not your regular password)
  • For Gmail: Ensure 2-Factor Authentication is enabled
  • Try regenerating your app password

Issue 3: Connection Timeout

Problem: Timeout when trying to connect to SMTP server

Solutions:

# Test if you can reach the SMTP server
telnet smtp.gmail.com 587

# If timeout, your firewall might be blocking it
# Try using port 465 with SSL instead:
MAIL_PORT=465
MAIL_ENCRYPTION=ssl

Issue 4: Verification Link Not Working

Problem: Clicking verification link shows “Invalid signature” or 404 error

Solutions:

  • Check your APP_URL in .env matches your actual URL:
APP_URL=http://127.0.0.1:8000
  • Clear config cache: php artisan config:clear
  • Make sure the link hasn’t expired (default: 60 minutes)

Issue 5: Email Goes to Spam

Problem: Verification emails end up in spam folder

Solutions:

  • Use a professional email service (SendGrid, Mailgun, AWS SES)
  • Verify your domain with your email provider
  • Set up SPF, DKIM, and DMARC records
  • Don’t use Gmail for production

Advanced: Customizing the Verification Email

Want to customize the verification email template? Laravel makes it easy!

Publish the Email Templates

php artisan vendor:publish --tag=laravel-mail

Edit Template

File: resources/views/vendor/mail/html/header.blade.php

Customize:

  • Logo
  • Colors
  • Styling
  • Footer

Create a Custom Notification

For complete control, create a custom notification:

php artisan make:notification VerifyEmailNotification

Then in your User model:

use App\Notifications\VerifyEmailNotification;

public function sendEmailVerificationNotification()
{
    $this->notify(new VerifyEmailNotification);
}

Happy coding!!!

Resources