
How to add custom email verification in laravel 12
How to Add Custom Email Verification in Laravel 12
In this guide, you’ll learn how to implement custom email verification in Laravel 12 using a simple mailer system. This approach will allow you to have full control over the email verification logic, style, and flow, instead of relying on Laravel’s default implementation.
Verification Workflow Overview
- Configure email settings in
.env
- Generate App Password (for Gmail)
- Create custom mail class using
php artisan make:mail
- Encrypt and send a verification link
- Decrypt email from the verification link
- Mark user as verified
- Display custom email verification template
Step 1: Email Config in .env
Update your .env
file with your SMTP credentials:
MAIL_MAILER=smtp MAIL_HOST=smtp.gmail.com MAIL_PORT=587 MAIL_USERNAME=example@gmail.com MAIL_PASSWORD=mhzodjvkwleqns MAIL_ENCRYPTION=tls MAIL_FROM_ADDRESS=example@gmail.com MAIL_FROM_NAME="My App"
⚠️ Note: If you’re using Gmail, make sure you’ve created an App Password under your Google Account security settings.
Step 2: Create the Mail Class
Run the following artisan command:
php artisan make:mail VerifyUser
App\Mail\VerifyUser.php
namespace App\Mail; use Illuminate\Bus\Queueable; use Illuminate\Mail\Mailable; use Illuminate\Queue\SerializesModels; class VerifyUser extends Mailable { use Queueable, SerializesModels; public $link; public function __construct($link) { $this->link = $link; } public function build() { return $this->view('mail.user-verify')->with([ 'link' => $this->link, ]); } }
Step 3: Create the Verification Email View
php artisan make:view mail.user-verify
resources/views/mail/user-verify.blade.php
:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Email Verification</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <style> body { font-family: Arial, sans-serif; background: #f8f9fa; margin: 0; padding: 0; } .container { background: #fff; max-width: 600px; margin: 30px auto; padding: 30px; border-radius: 8px; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); } .header { font-size: 22px; font-weight: bold; color: #343a40; } .message { font-size: 16px; color: #495057; margin: 20px 0; } .button { background-color: #0d6efd; padding: 12px 24px; color: white; text-decoration: none; border-radius: 5px; } .footer { font-size: 13px; color: #6c757d; text-align: center; margin-top: 25px; } </style> </head> <body> <div class="container"> <div class="header">Verify Your Email Address</div> <div class="message"> <p>Hello,</p> <p>Thank you for registering. Please verify your email address by clicking the button below.</p> <p><strong>This link will expire in 10 minutes.</strong></p> </div> <div style="text-align: center;"> <a href="{{ $link }}" class="button">Verify Email</a> </div> <div class="message"> If the button doesn't work, copy and paste this link: <p><a href="{{ $link }}">{{ $link }}</a></p> </div> <div class="footer">If you did not request this email, you can ignore it.</div> </div> </body> </html>
Step 4: Create the Route
In your routes/web.php
:
Route::get('/account/verify-user/{email}', [LoginController::class, 'VerifyUser'])->name('account.verify-user');
Step 5: Update Your Registration Logic
In your LoginController.php
or RegisterController.php
, add the logic:
use Illuminate\Http\Request; use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Mail; use App\Models\User; use App\Mail\VerifyUser; public function processRegister(Request $request) { $validator = Validator::make($request->all(), [ 'name' => 'required', 'email' => 'required|email|unique:users', 'password' => 'required|confirmed|min:6', ]); if ($validator->fails()) { return redirect()->route('account.register')->withErrors($validator)->withInput(); } $user = new User(); $user->name = $request->name; $user->username = $request->name; $user->email = $request->email; $user->password = Hash::make($request->password); $user->role = 'user'; $user->save(); $encryptedEmail = Crypt::encryptString($request->email); $verificationLink = url('/account/verify-user/' . $encryptedEmail); try { Mail::to($request->email)->send(new VerifyUser($verificationLink)); return redirect()->route('account.login')->with('success', 'You have successfully registered. Please check your email.'); } catch (\Exception $e) { \Log::error("Email sending failed: " . $e->getMessage()); return redirect()->back()->with('error', 'Could not send verification email.'); } }
Step 6: Email Verification Logic
In your LoginController.php
:
use Illuminate\Support\Carbon; public function VerifyUser($email) { try { $orgEmail = Crypt::decryptString($email); $user = User::where('email', $orgEmail)->first(); if ($user) { $user->email_verified_at = Carbon::now(); $user->save(); return redirect()->route('account.login')->with('success', 'Email verified successfully.'); } return redirect()->route('account.login')->with('error', 'User not found.'); } catch (\Exception $e) { return redirect()->route('account.login')->with('error', 'Invalid or expired verification link.'); } }
Final Thoughts
Now you’ve successfully implemented custom email verification in Laravel 12!
This gives you:
- Full control over email design
- Better error handling
- Custom expiration, styling, and behavior
- Compatibility with any SMTP server (like Gmail, Mailgun, etc.)
Bonus Tip
Use signed routes with expiration using Laravel’s URL::temporarySignedRoute
to improve security.
Need that too? I can help you add it!