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:
- Prevents Fake Accounts – Users can’t register with fake or mistyped email addresses
- Reduces Spam – Bots have a harder time creating multiple accounts
- Ensures Communication – You can actually reach your users via email
- Improves Security – Confirms that the email owner authorized the registration
- 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?
- Added
implements MustVerifyEmailafter the class declaration - Made sure
email_verified_atis 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:
event(new Registered($user))– This line triggers Laravel to send the verification email automaticallyAuth::login($user)– We log the user in immediately (but they still can’t access protected pages until verified)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.
- Sign up at https://mailtrap.io (free)
- Get your credentials from the demo inbox
- 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:
- Enable 2-Factor Authentication on your Gmail account
- 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
- 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.
- Sign up at https://sendgrid.com
- Free tier: 100 emails/day
- Paid: Starting at $19.95/month for 50,000 emails
- Verify your sender identity (domain or single sender)
- Create an API Key
- 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 serveVisit 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_URLin.envmatches 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-mailEdit 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 VerifyEmailNotificationThen in your User model:
use App\Notifications\VerifyEmailNotification;
public function sendEmailVerificationNotification()
{
$this->notify(new VerifyEmailNotification);
}Happy coding!!!
Resources
- Laravel Email Verification Documentation
- Laravel Mail Documentation
- SendGrid Documentation
- Mailtrap Documentation
