Validate emails in Laravel forms

A Laravel validation rule that fails an email address that cannot receive email, in any form request or Validator::make call. It is built on the official PHP client library, checks each address once, and never stops a real person from signing up when the API cannot answer in time.

use EmailValidation\Laravel\ValidEmail;

$request->validate([
    'email' => ['bail', 'required', 'email', new ValidEmail()],
]);

Install

The package is not on Packagist yet. Download it here and install it from a folder of your app:

  1. Download email-validation-laravel-0.1.0.zip and save it in a folder of your project, for example packages/.

  2. Add that folder as a Composer repository and require the package:

    composer config repositories.email-validation-laravel artifact ./packages
    composer require email-validation/laravel:^0.1

    Composer installs the PHP client library from Packagist. Laravel discovers the service provider by itself.

  3. Create an API key on the API keys page with the permission "Validate addresses", and give it a daily credit cap to limit what a busy form can spend, and put the form's route behind Laravel's throttle middleware: each new address a visitor sends is a paid check. Put the key in .env:

    EV_API_KEY=your-api-key

    The key is read from your configuration only; never put it in code.

It needs Laravel 10 or newer and PHP 8.1 or newer.

Use the rule

Add the rule object, or the string rule, after Laravel's own email rule. With bail, a malformed address stops at email and is never sent.

'email' => ['bail', 'required', 'email', new ValidEmail()],

// fail disposable addresses too, and refuse the form when the check cannot finish
'email' => ['bail', 'required', 'email', new ValidEmail(failOn: ['invalid', 'disposable'], failOpen: false)],

// the string rule: the project's settings, or the failing statuses as parameters
'email' => 'bail|required|email|valid_email',
'email' => 'bail|required|email|valid_email:invalid,disposable',

Settings

Publish the settings to config/email-validation.php to change them:

php artisan vendor:publish --tag=email-validation-config
'api_key' => env('EV_API_KEY'),
'base_url' => env('EV_BASE_URL', 'https://api.email-validation.org'),
'fail_on' => ['invalid'],
'block_disposable' => (bool) env('EV_BLOCK_DISPOSABLE', false),
'fail_open' => (bool) env('EV_FAIL_OPEN', true),
'timeout' => 5,
'client_timeout' => 8,
'cache_store' => env('EV_CACHE_STORE'),
'cache_ttl' => 3600,
  • Which statuses fail. Only invalid by default. Set EV_BLOCK_DISPOSABLE=true to fail disposable addresses as well. catch_all and unknown pass unless you add them: see what each status means.
  • When the check cannot finish (it timed out, the API could not be reached, it answered with a 5xx or 429 error, the account has no credits or no key is set), the address passes and a warning goes to your log with the error code and request id, never the address. Set EV_FAIL_OPEN=false to fail the field with "We could not check the email right now" instead.
  • EU data residency. Set EV_BASE_URL=https://api-eu.email-validation.org; the same key works on both.

Speed and caching

The rule gives the API 5 seconds for the check (the API takes 5 to 15) and waits 8 seconds for the answer, without retrying: a slow mail server then answers unknown, which passes and is free.

Each result is kept in your cache for an hour, under a SHA-256 hash of the trimmed, lower-cased address, so a form sent again after fixing another field is not checked or charged again. Only the status is kept, never the address, and unknown results are not kept.

Messages

The messages are in the translation namespace email-validation. Publish them to change or translate them:

php artisan vendor:publish --tag=email-validation-lang

A message for one form, such as 'email.valid_email' => 'Please use another address.', also works with the string rule. The messages never show the API's own error text.

Costs

Each completed check costs one credit. Results with the status unknown are free, and so are malformed addresses and the test addresses: try the rule with accepted@sandbox.email-validation.org (passes) and no_mailbox@sandbox.email-validation.org (fails) at no cost. See credits and billing.

Updated · Maintained by the email-validation.org team