Developer Guide 10 min read

Adding Email Validation to Laravel: A Step-by-Step Guide

Matt King

Matt King

October 1, 2025

Adding Email Validation to Laravel: A Step-by-Step Guide

Laravel does not include built-in disposable email detection. Its email validation rule only checks format and optionally DNS — it cannot identify throwaway providers, check domain reputation, or assign a risk score. For that, you need an external validation API.

This guide walks through a complete integration: service class, custom validation rule, caching, and async fallback.

What You'll Build

  1. A FidroService class that wraps the API calls
  2. A custom NotDisposableEmail validation rule
  3. Redis caching to minimize API calls
  4. A queue job for async validation fallback

Step 1: Add Your API Key

# .env
FIDRO_API_KEY=your_api_key_here
// config/services.php
'fidro' => [
    'api_key' => env('FIDRO_API_KEY'),
    'base_url' => env('FIDRO_BASE_URL', 'https://api.fidro.io/v1'),
],

Step 2: Create the Service Class

<?php
// app/Services/FidroService.php
namespace App\Services;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

class FidroService
{
    public function validateEmail(string $email): ?array
    {
        $cacheKey = 'fidro:email:' . md5($email);

        return Cache::remember($cacheKey, now()->addHours(24), function () use ($email) {
            try {
                $response = Http::timeout(3)
                    ->withHeaders([
                        'Authorization' => 'Bearer ' . config('services.fidro.api_key'),
                    ])
                    ->post(config('services.fidro.base_url') . '/validate/email', [
                        'email' => $email,
                    ]);

                if ($response->successful()) {
                    return $response->json();
                }

                Log::warning('Fidro API error', [
                    'status' => $response->status(),
                    'email' => $email,
                ]);

                return null;
            } catch (\Exception $e) {
                Log::warning('Fidro API unreachable', [
                    'error' => $e->getMessage(),
                    'email' => $email,
                ]);

                return null; // Fail open
            }
        });
    }

    public function isDisposable(string $email): bool
    {
        $result = $this->validateEmail($email);
        return $result['disposable'] ?? false;
    }

    public function riskScore(string $email): float
    {
        $result = $this->validateEmail($email);
        return $result['risk_score'] ?? 0.0;
    }
}

Step 3: Create the Validation Rule

<?php
// app/Rules/NotDisposableEmail.php
namespace App\Rules;

use App\Services\FidroService;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

class NotDisposableEmail implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $fidro = app(FidroService::class);
        $result = $fidro->validateEmail($value);

        // Fail open: if API is unreachable, allow the email
        if ($result === null) {
            return;
        }

        if ($result['disposable'] ?? false) {
            $fail('Please use a permanent email address. Temporary emails are not accepted.');
        }

        if (($result['risk_score'] ?? 0) > 0.7) {
            $fail('This email address could not be verified. Please try a different one.');
        }
    }
}

Step 4: Use It in Registration

// In your registration controller or form request
use App\Rules\NotDisposableEmail;

$request->validate([
    'name' => ['required', 'string', 'max:255'],
    'email' => ['required', 'string', 'email', 'max:255', 'unique:users', new NotDisposableEmail],
    'password' => ['required', 'confirmed', 'min:8'],
]);

This works identically with Laravel Breeze, Jetstream, and Fortify. Just add the rule to the existing validation array.

Step 5: Async Fallback with Queue Jobs

For cases where the API times out during signup, queue a background check:

<?php
// app/Jobs/ValidateUserEmail.php
namespace App\Jobs;

use App\Models\User;
use App\Services\FidroService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class ValidateUserEmail implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct(public User $user) {}

    public function handle(FidroService $fidro): void
    {
        $result = $fidro->validateEmail($this->user->email);

        if ($result && ($result['disposable'] ?? false)) {
            $this->user->update([
                'email_flagged' => true,
                'email_flag_reason' => 'disposable',
            ]);
        }
    }
}

Dispatch it after registration:

ValidateUserEmail::dispatch($user)->delay(now()->addSeconds(5));

Testing the Integration

Mock the API in your tests so you never call the real endpoint:

public function test_disposable_emails_are_rejected(): void
{
    Http::fake([
        'api.fidro.io/*' => Http::response([
            'email' => 'test@mailinator.com',
            'disposable' => true,
            'risk_score' => 0.9,
        ]),
    ]);

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

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

Next Steps

  1. Get your free API key — 200 validations/month included
  2. Check the API documentation for the complete response schema
  3. Try the free email checker to test addresses interactively

Frequently Asked Questions

Does Laravel have built-in disposable email detection?

No. Laravel's built-in email validation only checks format (RFC compliance) and optionally DNS records. It cannot detect disposable email providers, check domain reputation, or score risk. You need an external API like Fidro for that.

Should I validate emails synchronously or asynchronously in Laravel?

Use synchronous validation at signup to block disposable emails immediately. Use asynchronous validation (via Laravel queues) for bulk operations, re-checking existing users, or as a fallback when the API times out during synchronous checks.

How do I add Fidro to an existing Laravel project?

Three steps: add your API key to .env (FIDRO_API_KEY=your_key), create a service class that wraps the HTTP calls, and create a custom validation rule that uses the service. The entire integration takes under 30 minutes.

Will this work with Laravel Jetstream or Breeze?

Yes. Both Jetstream and Breeze use standard Laravel validation rules in their registration controllers. Add the custom Fidro validation rule to the existing rules array and it works immediately with no other changes needed.

How do I test email validation in my Laravel test suite?

Use Http::fake() to mock the Fidro API responses in your tests. Create factory methods that return disposable and non-disposable responses, then assert your application handles each case correctly. Never call the real API in automated tests.