{"id":161,"date":"2025-08-26T14:10:58","date_gmt":"2025-08-26T14:10:58","guid":{"rendered":"https:\/\/1v0.net\/blog\/?p=161"},"modified":"2025-08-26T14:11:00","modified_gmt":"2025-08-26T14:11:00","slug":"how-to-build-email-verification-in-laravel-12-step-by-step","status":"publish","type":"post","link":"https:\/\/1v0.net\/blog\/how-to-build-email-verification-in-laravel-12-step-by-step\/","title":{"rendered":"How to Build Email Verification in Laravel 12 (Step by Step)"},"content":{"rendered":"\n<p>One of the first security features you should add to any web app is <strong>Email Verification<\/strong>. When a new user signs up, you want to be sure the email they provided is valid and belongs to them. Without verification, users could register with fake or disposable emails, which leads to spam, weak security, and a lack of trust in your application.<\/p>\n\n\n\n<p>In this step-by-step tutorial, we\u2019ll build a complete <strong>Email Verification system in Laravel 12<\/strong>. We\u2019ll explain not just the code, but also why each step matters, so even beginners can follow along. By the end, your app will send verification emails, require verified emails for protected pages, and provide a simple UI for resending verification links.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>1 &#8211; What is Email Verification?<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n\n<p><strong>Email Verification<\/strong> means a new user must confirm ownership of their email before they can fully use your app. The system sends a unique, signed link to their inbox. Clicking the link marks their account as verified. This prevents fake accounts, strengthens security, and ensures you can reach users reliably (for password resets, notifications, etc.).<\/p>\n\n\n\n<p>Laravel 12 includes built-in verification support. We\u2019ll turn it on, add routes and UI, and protect pages with the <code>verified<\/code> middleware.<\/p>\n\n\n\n<p><\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>2 &#8211; Prerequisites<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Laravel 12 project (PHP 8.2+) with a working database connection in <code>.env<\/code><\/li>\n<li>Basic auth (register &amp; login) already in place (can be hand-rolled or any scaffolding)<\/li>\n<li>Mail configured in <code>.env<\/code> (SMTP\/Mailgun\/Sendgrid\/etc.) so the app can send emails<\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>3 &#8211; Enable Email Verification on the User model<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n\n<p>Add the <code>MustVerifyEmail<\/code> interface to your <code>User<\/code> model. This tells Laravel that users must verify their email before accessing routes protected by the <code>verified<\/code> middleware.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-code-table shcb-line-numbers shcb-wrap-lines\"><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ app\/Models\/User.php<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">namespace<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Models<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Contracts<\/span>\\<span class=\"hljs-title\">Auth<\/span>\\<span class=\"hljs-title\">MustVerifyEmail<\/span>; <span class=\"hljs-comment\">\/\/ &lt;-- add this<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Foundation<\/span>\\<span class=\"hljs-title\">Auth<\/span>\\<span class=\"hljs-title\">User<\/span> <span class=\"hljs-title\">as<\/span> <span class=\"hljs-title\">Authenticatable<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Notifications<\/span>\\<span class=\"hljs-title\">Notifiable<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Database<\/span>\\<span class=\"hljs-title\">Eloquent<\/span>\\<span class=\"hljs-title\">Factories<\/span>\\<span class=\"hljs-title\">HasFactory<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">User<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Authenticatable<\/span> <span class=\"hljs-keyword\">implements<\/span> <span class=\"hljs-title\">MustVerifyEmail<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><\/span>{\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">HasFactory<\/span>, <span class=\"hljs-title\">Notifiable<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-comment\">\/\/ fillable\/hidden\/casts as you normally have them<\/span>\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><strong>Explanation:<\/strong> implementing <code>MustVerifyEmail<\/code> enables a few framework behaviors: Laravel knows how to send verification notifications, and the <code>verified<\/code> middleware will check <code>$user->hasVerifiedEmail()<\/code> for protected routes.<\/p>\n\n\n\n<p><\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>4 &#8211; Add the verification routes (notice, verify, resend)<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n\n<p>Create three routes: a \u201cplease verify your email\u201d page, the <em>confirmation<\/em> endpoint (clicked from email), and a <em>resend<\/em> endpoint (rate-limited) in case the user didn\u2019t receive the email.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-code-table shcb-line-numbers shcb-wrap-lines\"><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ routes\/web.php<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Foundation<\/span>\\<span class=\"hljs-title\">Auth<\/span>\\<span class=\"hljs-title\">EmailVerificationRequest<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Request<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ 1) Notice page (unverified users land here)<\/span>\n<\/span><\/span><span class='shcb-loc'><span>Route::get(<span class=\"hljs-string\">'\/email\/verify'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">()<\/span> <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span> view(<span class=\"hljs-string\">'auth.verify-email'<\/span>); <span class=\"hljs-comment\">\/\/ we'll create this view next<\/span>\n<\/span><\/span><span class='shcb-loc'><span>})-&gt;middleware(<span class=\"hljs-string\">'auth'<\/span>)-&gt;name(<span class=\"hljs-string\">'verification.notice'<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ 2) Verification callback (user clicks link in email)<\/span>\n<\/span><\/span><span class='shcb-loc'><span>Route::get(<span class=\"hljs-string\">'\/email\/verify\/{id}\/{hash}'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">(EmailVerificationRequest $request)<\/span> <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>    $request-&gt;fulfill(); <span class=\"hljs-comment\">\/\/ marks the user's email as verified<\/span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span> redirect(<span class=\"hljs-string\">'\/dashboard'<\/span>); <span class=\"hljs-comment\">\/\/ or wherever you want verified users to land<\/span>\n<\/span><\/span><span class='shcb-loc'><span>})-&gt;middleware(&#91;<span class=\"hljs-string\">'auth'<\/span>, <span class=\"hljs-string\">'signed'<\/span>])-&gt;name(<span class=\"hljs-string\">'verification.verify'<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ 3) Resend verification link (rate-limited)<\/span>\n<\/span><\/span><span class='shcb-loc'><span>Route::post(<span class=\"hljs-string\">'\/email\/verification-notification'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">(Request $request)<\/span> <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>    $request-&gt;user()-&gt;sendEmailVerificationNotification();\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span> back()-&gt;with(<span class=\"hljs-string\">'status'<\/span>, <span class=\"hljs-string\">'Verification link sent!'<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>})-&gt;middleware(&#91;<span class=\"hljs-string\">'auth'<\/span>, <span class=\"hljs-string\">'throttle:6,1'<\/span>])-&gt;name(<span class=\"hljs-string\">'verification.send'<\/span>);\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><strong>Explanation:<\/strong> The <code>signed<\/code> middleware ensures the verification link has not been tampered with. The <code>throttle:6,1<\/code> middleware lets users request a new link but prevents abuse (max 6 per minute).<\/p>\n\n\n\n<p><\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>5 &#8211; Build the \u201cVerify your email\u201d Blade view (with resend button)<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n\n<p>Create a friendly, minimal UI that explains why verification is needed and allows resending the email.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-code-table shcb-line-numbers shcb-wrap-lines\"><span class='shcb-loc'><span>&lt;!-- resources\/views\/auth\/verify-email.blade.php --&gt;\n<\/span><\/span><span class='shcb-loc'><span>@extends(<span class=\"hljs-string\">'layouts.app'<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>@section(<span class=\"hljs-string\">'content'<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>&lt;div <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span>=\"<span class=\"hljs-title\">container<\/span> <span class=\"hljs-title\">py<\/span>-5\" <span class=\"hljs-title\">style<\/span>=\"<span class=\"hljs-title\">max<\/span>-<span class=\"hljs-title\">width<\/span>:720<span class=\"hljs-title\">px<\/span>\"&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\">  &lt;<span class=\"hljs-title\">h1<\/span> <span class=\"hljs-title\">class<\/span>=\"<span class=\"hljs-title\">h4<\/span> <span class=\"hljs-title\">mb<\/span>-3\"&gt;<span class=\"hljs-title\">Verify<\/span> <span class=\"hljs-title\">your<\/span> <span class=\"hljs-title\">email<\/span> <span class=\"hljs-title\">address<\/span>&lt;\/<span class=\"hljs-title\">h1<\/span>&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\">  &lt;<span class=\"hljs-title\">p<\/span>&gt;<span class=\"hljs-title\">Thanks<\/span> <span class=\"hljs-title\">for<\/span> <span class=\"hljs-title\">signing<\/span> <span class=\"hljs-title\">up<\/span>! <span class=\"hljs-title\">Before<\/span> <span class=\"hljs-title\">getting<\/span> <span class=\"hljs-title\">started<\/span>, <span class=\"hljs-title\">please<\/span> <span class=\"hljs-title\">verify<\/span> <span class=\"hljs-title\">your<\/span> <span class=\"hljs-title\">email<\/span> <span class=\"hljs-title\">by<\/span> <span class=\"hljs-title\">clicking<\/span> <span class=\"hljs-title\">the<\/span> <span class=\"hljs-title\">link<\/span> <span class=\"hljs-title\">we<\/span> <span class=\"hljs-title\">just<\/span> <span class=\"hljs-title\">sent<\/span> <span class=\"hljs-title\">you<\/span>.&lt;\/<span class=\"hljs-title\">p<\/span>&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\">  @<span class=\"hljs-title\">if<\/span> (<span class=\"hljs-title\">session<\/span>('<span class=\"hljs-title\">status<\/span>') === '<span class=\"hljs-title\">verification<\/span>-<span class=\"hljs-title\">link<\/span>-<span class=\"hljs-title\">sent<\/span>')<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\">    &lt;<span class=\"hljs-title\">div<\/span> <span class=\"hljs-title\">class<\/span>=\"<span class=\"hljs-title\">alert<\/span> <span class=\"hljs-title\">alert<\/span>-<span class=\"hljs-title\">success<\/span> <span class=\"hljs-title\">mt<\/span>-3\"&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\">      <span class=\"hljs-title\">A<\/span> <span class=\"hljs-title\">new<\/span> <span class=\"hljs-title\">verification<\/span> <span class=\"hljs-title\">link<\/span> <span class=\"hljs-title\">has<\/span> <span class=\"hljs-title\">been<\/span> <span class=\"hljs-title\">sent<\/span> <span class=\"hljs-title\">to<\/span> <span class=\"hljs-title\">your<\/span> <span class=\"hljs-title\">email<\/span> <span class=\"hljs-title\">address<\/span>.<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\">    &lt;\/<span class=\"hljs-title\">div<\/span>&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\">  @<span class=\"hljs-title\">endif<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\">  &lt;<span class=\"hljs-title\">form<\/span> <span class=\"hljs-title\">method<\/span>=\"<span class=\"hljs-title\">POST<\/span>\" <span class=\"hljs-title\">action<\/span>=\"<\/span>{{ route(<span class=\"hljs-string\">'verification.send'<\/span>) }}<span class=\"hljs-string\">\" class=\"<\/span>mt<span class=\"hljs-number\">-3<\/span><span class=\"hljs-string\">\"&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">    @csrf<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">    &lt;button class=\"<\/span>btn btn-primary<span class=\"hljs-string\">\"&gt;Resend verification email&lt;\/button&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">  &lt;\/form&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\"><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">  &lt;p class=\"<\/span>text-muted small mt<span class=\"hljs-number\">-3<\/span> mb<span class=\"hljs-number\">-0<\/span><span class=\"hljs-string\">\"&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">    Didn\u2019t get the email? Check spam, or click the button above to resend.<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">  &lt;\/p&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">&lt;\/div&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">@endsection<\/span><\/span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><strong>Explanation:<\/strong> We use a neutral success message so users don\u2019t wonder if the button \u201cworked.\u201d The message displays whenever a new link is sent.<\/p>\n\n\n\n<p><\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>6 &#8211; Protect pages with the <code>verified<\/code> middleware<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n\n<p>Add the <code>verified<\/code> middleware to any route that should only be accessible after email confirmation. Unverified users are redirected to the notice page.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-code-table shcb-line-numbers shcb-wrap-lines\"><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ routes\/web.php<\/span>\n<\/span><\/span><span class='shcb-loc'><span>Route::get(<span class=\"hljs-string\">'\/dashboard'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">()<\/span> <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span> view(<span class=\"hljs-string\">'dashboard'<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>})-&gt;middleware(&#91;<span class=\"hljs-string\">'auth'<\/span>, <span class=\"hljs-string\">'verified'<\/span>]);\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><strong>Explanation:<\/strong> The middleware calls <code>$request->user()->hasVerifiedEmail()<\/code>. If it returns <code>false<\/code>, the user is redirected to <code>verification.notice<\/code> (<code>\/email\/verify<\/code>).<\/p>\n\n\n\n<p><\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>7 &#8211; Configure mail so emails actually send<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n\n<p>Make sure your <code>.env<\/code> is configured, otherwise emails will silently fail or go nowhere.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\"># .env (example SMTP settings)<\/span>\nMAIL_MAILER=smtp\nMAIL_HOST=smtp.your-provider.com\nMAIL_PORT=<span class=\"hljs-number\">587<\/span>\nMAIL_USERNAME=your_user\nMAIL_PASSWORD=your_password\nMAIL_ENCRYPTION=tls\nMAIL_FROM_ADDRESS=no-reply@your-domain.com\nMAIL_FROM_NAME=<span class=\"hljs-string\">\"${APP_NAME}\"<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><strong>Explanation:<\/strong> Test quickly by registering a new account in local\/dev. If you don\u2019t want to send real emails locally, use a mail catcher (MailHog\/HELO) and point SMTP to it.<\/p>\n\n\n\n<p><\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>8 &#8211; (Optional) Customize the verification email<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n\n<p>Laravel uses a notification to send the verification email. You can customize the email by overriding the notification\u2019s <code>toMail<\/code> on a <code>VerifyEmail<\/code> class and registering it, or by customizing mail styling with Markdown mail templates. A lightweight approach is to customize <code>MAIL_FROM_*<\/code> and branding in your layout so it aligns with your app\u2019s voice.<\/p>\n\n\n\n<p>(If you later need a branded template, create a custom notification that extends the default and uses your Markdown view. Keep the signed URL intact for security.)<\/p>\n\n\n\n<p><\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>9 &#8211; Test the entire flow (step-by-step)<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Register a new user (ensure mail settings are correct).<\/li>\n<li>Try visiting a protected page (like <code>\/dashboard<\/code>) \u2014 you should be redirected to <code>\/email\/verify<\/code>.<\/li>\n<li>Open your inbox and click the verification link.<\/li>\n<li>You should be redirected and now able to access <code>\/dashboard<\/code>.<\/li>\n<li>Try the \u201cResend verification email\u201d button if needed.<\/li>\n<\/ol>\n\n\n\n<p><\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>10 &#8211; Common errors &amp; fixes<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Verification email not received:<\/strong> Double-check <code>.env<\/code> SMTP credentials and logs. Use a mail catcher locally. Check spam folders in staging\/production.<\/li>\n<li><strong>\u201cInvalid signature\u201d error:<\/strong> The verification link was modified or expired. Ask the user to resend the link from the verify page.<\/li>\n<li><strong>Still redirected to verify after clicking link:<\/strong> Ensure the <code>User<\/code> model implements <code>MustVerifyEmail<\/code>, and your protected routes include the <code>verified<\/code> middleware.<\/li>\n<li><strong>Local dev with tunnels:<\/strong> If using a tunnel (e.g., ngrok), ensure the URL in the email matches the environment you\u2019re testing in.<\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Wrapping Up<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n\n<p>You\u2019ve built a complete <strong>Email Verification flow in Laravel 12<\/strong>: enabling verification on the <code>User<\/code> model, adding secure routes, creating a clean UI with a resend button, and protecting sensitive pages with the <code>verified<\/code> middleware. This simple feature dramatically improves trust and safety in your application.<\/p>\n\n\n\n<p><\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>What\u2019s Next<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/blog\/implementing-two-factor-authentication-in-laravel\">Implementing Two-Factor Authentication in Laravel<\/a> \u2014 add another layer of login protection.<\/li>\n<li><a href=\"\/blog\/how-to-restrict-page-access-by-role-in-laravel-12\">How to Restrict Page Access by Role in Laravel 12<\/a> \u2014 ensure only the right users see the right pages.<\/li>\n<li><a href=\"\/blog\/laravel-authentication-redirect-after-login-logout\">Laravel Authentication: Redirect Users After Login &amp; Logout<\/a> \u2014 polish the user journey.<\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n","protected":false},"excerpt":{"rendered":"<p>One of the first security features you should add to any web app is Email Verification. When a new user signs up, you want to be sure the email they provided is valid and belongs to them. Without verification, users could register with fake or disposable emails, which leads to spam, weak security, and a [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":165,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[12,22],"class_list":["post-161","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-laravel","tag-authentication","tag-security"],"_links":{"self":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/161","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/comments?post=161"}],"version-history":[{"count":1,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/161\/revisions"}],"predecessor-version":[{"id":164,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/161\/revisions\/164"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/media\/165"}],"wp:attachment":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/media?parent=161"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/categories?post=161"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/tags?post=161"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}