EmailVerify LogoEmailVerify

Laravel

Email checker for Laravel. PHP email verification in Laravel controllers and forms.

Add email verification to your Laravel application using custom validation rules, middleware, and service providers.

Installation

composer require emailverify/laravel

Or install the PHP SDK directly:

composer require emailverify/php-sdk

Configuration

Publish Configuration

php artisan vendor:publish --tag=emailverify-config

Environment Variables

# .env
EMAILVERIFY_API_KEY=bv_live_xxx

Config File

// config/emailverify.php
return [
    'api_key' => env('EMAILVERIFY_API_KEY'),
    'timeout' => 10,
    'cache_duration' => 3600, // 1 hour
    'block_disposable' => true,
    'block_role_based' => false,
];

Service Provider

Register the service provider if not using auto-discovery:

// config/app.php
'providers' => [
    EmailVerify\Laravel\EmailVerifyServiceProvider::class,
],

'aliases' => [
    'EmailVerify' => EmailVerify\Laravel\Facades\EmailVerify::class,
],

Validation Rule

Using the Built-in Rule

use App\Http\Requests\RegisterRequest;

class RegisterRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'email' => ['required', 'email', 'emailverify'],
            'name' => ['required', 'string', 'max:255'],
            'password' => ['required', 'confirmed', 'min:8'],
        ];
    }

    public function messages(): array
    {
        return [
            'email.emailverify' => 'Please provide a valid email address.',
        ];
    }
}

Custom Validation Rule

// app/Rules/ValidEmail.php
<?php

namespace App\Rules;

use EmailVerify\Client;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

class ValidEmail implements ValidationRule
{
    protected bool $blockDisposable;
    protected bool $blockRoleBased;

    public function __construct(
        bool $blockDisposable = true,
        bool $blockRoleBased = false
    ) {
        $this->blockDisposable = $blockDisposable;
        $this->blockRoleBased = $blockRoleBased;
    }

    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $client = new Client(config('emailverify.api_key'));

        try {
            $result = $client->verify($value);

            if ($result->status === 'invalid') {
                $fail('The :attribute must be a valid email address.');
                return;
            }

            if ($this->blockDisposable && $result->result->disposable) {
                $fail('Disposable email addresses are not allowed.');
                return;
            }

            if ($this->blockRoleBased && $result->result->role) {
                $fail('Role-based email addresses are not allowed.');
                return;
            }

        } catch (\Exception $e) {
            // Log error but don't block submission
            \Log::warning('EmailVerify API error', [
                'email' => $value,
                'error' => $e->getMessage(),
            ]);
        }
    }
}

Usage

use App\Rules\ValidEmail;

$request->validate([
    'email' => ['required', 'email', new ValidEmail(
        blockDisposable: true,
        blockRoleBased: false
    )],
]);

Facade Usage

use EmailVerify\Laravel\Facades\EmailVerify;

// Single verification
$result = EmailVerify::verify('user@example.com');

if ($result->status === 'valid') {
    // Email is valid
}

// Check specific properties
if (EmailVerify::isDisposable('user@example.com')) {
    // Block disposable email
}

if (EmailVerify::isValid('user@example.com')) {
    // Email is deliverable
}

Middleware

Create middleware for route-level verification.

Create Middleware

// app/Http/Middleware/VerifyEmailAddress.php
<?php

namespace App\Http\Middleware;

use EmailVerify\Client;
use Closure;
use Illuminate\Http\Request;

class VerifyEmailAddress
{
    protected Client $client;

    public function __construct()
    {
        $this->client = new Client(config('emailverify.api_key'));
    }

    public function handle(Request $request, Closure $next, string $field = 'email')
    {
        $email = $request->input($field);

        if (!$email) {
            return $next($request);
        }

        try {
            $result = $this->client->verify($email);

            if ($result->status === 'invalid') {
                return response()->json([
                    'error' => 'Invalid email address',
                ], 422);
            }

            // Add verification result to request
            $request->merge(['email_verification' => $result]);

        } catch (\Exception $e) {
            \Log::warning('Email verification failed', [
                'email' => $email,
                'error' => $e->getMessage(),
            ]);
        }

        return $next($request);
    }
}

Register Middleware

// bootstrap/app.php (Laravel 11+)
->withMiddleware(function (Middleware $middleware) {
    $middleware->alias([
        'verify.email' => \App\Http\Middleware\VerifyEmailAddress::class,
    ]);
})

// Or in app/Http/Kernel.php (Laravel 10)
protected $middlewareAliases = [
    'verify.email' => \App\Http\Middleware\VerifyEmailAddress::class,
];

Use in Routes

Route::post('/register', [AuthController::class, 'register'])
    ->middleware('verify.email');

Route::post('/subscribe', [NewsletterController::class, 'subscribe'])
    ->middleware('verify.email:subscriber_email');

Controller Integration

// app/Http/Controllers/AuthController.php
<?php

namespace App\Http\Controllers;

use App\Models\User;
use EmailVerify\Client;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;

class AuthController extends Controller
{
    protected Client $emailVerify;

    public function __construct()
    {
        $this->emailVerify = new Client(config('emailverify.api_key'));
    }

    public function register(Request $request)
    {
        $validated = $request->validate([
            'name' => 'required|string|max:255',
            'email' => 'required|email|unique:users',
            'password' => 'required|confirmed|min:8',
        ]);

        // Verify email
        $verification = $this->emailVerify->verify($validated['email']);

        if ($verification->status === 'invalid') {
            return back()->withErrors([
                'email' => 'Please provide a valid email address.',
            ]);
        }

        if ($verification->result->disposable) {
            return back()->withErrors([
                'email' => 'Disposable emails are not allowed.',
            ]);
        }

        // Create user
        $user = User::create([
            'name' => $validated['name'],
            'email' => $validated['email'],
            'password' => Hash::make($validated['password']),
            'email_verified_score' => $verification->score,
        ]);

        auth()->login($user);

        return redirect('/dashboard');
    }
}

Caching

Cache verification results to reduce API calls.

// app/Services/EmailVerificationService.php
<?php

namespace App\Services;

use EmailVerify\Client;
use Illuminate\Support\Facades\Cache;

class EmailVerificationService
{
    protected Client $client;
    protected int $cacheDuration;

    public function __construct()
    {
        $this->client = new Client(config('emailverify.api_key'));
        $this->cacheDuration = config('emailverify.cache_duration', 3600);
    }

    public function verify(string $email): object
    {
        $cacheKey = 'email_verification:' . md5($email);

        return Cache::remember($cacheKey, $this->cacheDuration, function () use ($email) {
            return $this->client->verify($email);
        });
    }

    public function isValid(string $email): bool
    {
        return $this->verify($email)->status === 'valid';
    }

    public function isDisposable(string $email): bool
    {
        return $this->verify($email)->result->disposable ?? false;
    }

    public function clearCache(string $email): void
    {
        Cache::forget('email_verification:' . md5($email));
    }
}

Service Provider Registration

// app/Providers/AppServiceProvider.php
public function register(): void
{
    $this->app->singleton(EmailVerificationService::class);
}

Usage

use App\Services\EmailVerificationService;

class SomeController extends Controller
{
    public function __construct(
        protected EmailVerificationService $emailVerification
    ) {}

    public function store(Request $request)
    {
        if (!$this->emailVerification->isValid($request->email)) {
            return back()->withErrors(['email' => 'Invalid email']);
        }

        // Continue processing
    }
}

Bulk Verification

Verify multiple emails efficiently.

// app/Console/Commands/VerifyUserEmails.php
<?php

namespace App\Console\Commands;

use App\Models\User;
use EmailVerify\Client;
use Illuminate\Console\Command;

class VerifyUserEmails extends Command
{
    protected $signature = 'users:verify-emails {--chunk=100}';
    protected $description = 'Verify all user email addresses';

    public function handle()
    {
        $client = new Client(config('emailverify.api_key'));
        $chunk = (int) $this->option('chunk');

        $users = User::whereNull('email_verified_at')
            ->orWhere('email_verification_date', '<', now()->subMonths(3))
            ->get();

        $this->info("Verifying {$users->count()} emails...");

        // Submit bulk job
        $emails = $users->pluck('email')->toArray();
        $job = $client->verifyBulk($emails);

        $this->info("Job ID: {$job->job_id}");

        // Poll for completion
        $bar = $this->output->createProgressBar(100);

        do {
            sleep(5);
            $status = $client->getBulkJobStatus($job->job_id);
            $bar->setProgress($status->progress_percent);
        } while ($status->status !== 'completed');

        $bar->finish();
        $this->newLine();

        // Get results and update users
        $results = $client->getBulkJobResults($job->job_id);

        foreach ($results->results as $result) {
            User::where('email', $result->email)->update([
                'email_verification_status' => $result->status,
                'email_verification_score' => $result->score,
                'email_verification_date' => now(),
            ]);
        }

        $this->info('Verification complete!');
        $this->table(
            ['Status', 'Count'],
            collect($results->results)
                ->groupBy('status')
                ->map(fn($items, $status) => [$status, $items->count()])
                ->values()
        );
    }
}

Event Listeners

React to verification results with events.

// app/Events/EmailVerified.php
<?php

namespace App\Events;

use Illuminate\Foundation\Events\Dispatchable;

class EmailVerified
{
    use Dispatchable;

    public function __construct(
        public string $email,
        public string $status,
        public float $score,
        public array $result
    ) {}
}

// app/Listeners/HandleEmailVerification.php
<?php

namespace App\Listeners;

use App\Events\EmailVerified;
use Illuminate\Support\Facades\Log;

class HandleEmailVerification
{
    public function handle(EmailVerified $event): void
    {
        if ($event->status === 'invalid') {
            Log::warning('Invalid email detected', [
                'email' => $event->email,
            ]);
        }

        if ($event->result['disposable'] ?? false) {
            Log::info('Disposable email blocked', [
                'email' => $event->email,
            ]);
        }
    }
}

Testing

Mock the Service

// tests/Feature/RegistrationTest.php
<?php

namespace Tests\Feature;

use EmailVerify\Client;
use Mockery;
use Tests\TestCase;

class RegistrationTest extends TestCase
{
    public function test_registration_with_valid_email(): void
    {
        $mock = Mockery::mock(Client::class);
        $mock->shouldReceive('verify')
            ->once()
            ->andReturn((object) [
                'status' => 'valid',
                'score' => 0.95,
                'result' => (object) [
                    'disposable' => false,
                    'role' => false,
                ],
            ]);

        $this->app->instance(Client::class, $mock);

        $response = $this->post('/register', [
            'name' => 'John Doe',
            'email' => 'john@example.com',
            'password' => 'password123',
            'password_confirmation' => 'password123',
        ]);

        $response->assertRedirect('/dashboard');
    }

    public function test_registration_blocks_disposable_email(): void
    {
        $mock = Mockery::mock(Client::class);
        $mock->shouldReceive('verify')
            ->once()
            ->andReturn((object) [
                'status' => 'valid',
                'score' => 0.5,
                'result' => (object) [
                    'disposable' => true,
                    'role' => false,
                ],
            ]);

        $this->app->instance(Client::class, $mock);

        $response = $this->post('/register', [
            'name' => 'John Doe',
            'email' => 'temp@mailinator.com',
            'password' => 'password123',
            'password_confirmation' => 'password123',
        ]);

        $response->assertSessionHasErrors('email');
    }
}

On this page