Laravel
Email checker for Laravel. Custom validation rules and form integration.
Add email verification to your Laravel application using custom validation rules, middleware, and service providers.
Installation
composer require emailverify/laravelOr install the PHP SDK directly:
composer require emailverify/php-sdkConfiguration
Publish Configuration
php artisan vendor:publish --tag=emailverify-configEnvironment Variables
# .env
EMAILVERIFY_API_KEY=bv_live_xxxConfig 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');
}
}