Validating an email address in Laravel is tempting to treat as a one-liner — add
email to a validation array and move on. That rule is a good start, but it
answers only the first of the three questions real validation asks. Is the
address shaped correctly? Can its domain receive mail? Does the mailbox actually
exist? Laravel is unusual in that its built-in email rule can answer the first
two in a single line; the third is a network problem worth handing off. This
guide builds each layer on top of Laravel's own validator and shows exactly
where the framework stops. It extends the
PHP email validation guide: Laravel wraps the
same PHP building blocks — and filter_var is one keyword away — in a much nicer
API.
The short answer#
Use Laravel's email:rfc,dns rule for syntax and the MX lookup in one pass, then
a verification API for the SMTP mailbox check — cheapest first, short-circuiting
with bail as soon as one layer is decisive. Don't try to open an SMTP session
from your app to probe mailboxes yourself: outbound port 25 is blocked on most
hosts, and the answer depends on sending-IP reputation and greylisting you don't
want to reimplement. Each layer rules addresses out more cheaply than the last;
only the final one can rule an address in.
Layers 1 and 2: syntax and DNS in one rule#
The email rule is backed by the egulias/email-validator package, and it takes
styles that decide how strict it is. By default it applies the rfc style
(RFC 5321/5322 grammar). The one that matters for validation is dns, which adds
a live lookup and confirms the domain resolves a mail route — an MX record, with
an A record as a fallback. Ask for both and Laravel does syntax and the domain
check in a single rule:
public function store(Request $request)
{
$validated = $request->validate([
'email' => ['required', 'email:rfc,dns'],
]);
// $validated['email'] is well-formed AND its domain can receive mail.
}
The other styles are worth knowing. email:filter runs PHP's
filter_var($email, FILTER_VALIDATE_EMAIL) — the exact check from the
PHP guide — and filter_unicode allows some
Unicode local parts. strict rejects the trailing- and consecutive-period
warnings the RFC parser tolerates. And spoof guards against deceptive homograph
characters, so for a public signup form email:rfc,dns,spoof is a sensible
default. In a form request that reads:
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreSubscriberRequest extends FormRequest
{
public function rules(): array
{
return [
'email' => ['required', 'email:rfc,dns,spoof'],
];
}
}
Why email:rfc,dns still isn't a green light#
A pass on email:rfc,dns means the string is well-formed and the domain can
receive mail. It says nothing about whether the specific mailbox exists.
noreply-9f2x@gmail.com passes. So does definitely-fake@gmail.com. Both are
undeliverable, because the MX record is a fact about the domain and the mailbox
is a fact you can only learn by asking the mail server. This is the same wall
that makes regex email validation fail:
no amount of pattern matching, and no DNS lookup, crosses from "the domain
accepts mail" to "this address does."
If you want to see the gap for yourself, paste a syntactically perfect address
into the free email checker and watch a rule-approved
string come back undeliverable.
Layer 3: does the mailbox actually exist?#
Confirming a specific mailbox means the SMTP delivery conversation: connect to the
mail host, issue RCPT TO, read the reply, and disconnect before sending
anything. In principle you could script this from PHP, but you shouldn't run it
from your application server — most cloud providers block outbound port 25, the
answer depends on the reputation of the IP you connect from, and receiving
servers greylist and rate-limit unfamiliar senders. There's more to it, too:
how email verification works walks the full
pipeline, including catch-all domains that accept every address and defeat a naive
probe. This is the layer worth delegating.
Calling Qualisend from a custom validation rule#
Laravel makes the delegation clean: wrap the API call in a rule object and drop it into the same rules array. Generate one with Artisan:
php artisan make:rule DeliverableEmail
Keep the key out of your code by wiring it through config/services.php, which
reads it from your .env:
// config/services.php
'qualisend' => [
'key' => env('QUALISEND_API_KEY'),
],
Then the rule calls the verify endpoint with the Http facade, reads the
result envelope, and fails only when the verdict is undeliverable:
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Http;
class DeliverableEmail implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$response = Http::withToken(config('services.qualisend.key'))
->timeout(10)
->post('https://api.qualisend.com/v1/verify', ['email' => $value]);
// On our own outage or a rate limit, don't block a real signup.
if ($response->failed()) {
return;
}
$result = $response->json('result');
if (($result['status'] ?? null) === 'undeliverable') {
$fail('This email address appears to be undeliverable.');
}
}
}
Http::withToken() sets the bearer auth header, ->post() sends the JSON body,
and $response->json('result') pulls the nested result object out with dot
access. The endpoint above is a placeholder — check the API reference
for the exact base URL and request shape — but the response reads back like this:
{
"result": {
"status": "deliverable",
"score": 95,
"reason": null,
"sub_flags": { "role": false, "disposable": false, "free": false, "catch_all": false }
}
}
status is one of deliverable, risky, undeliverable, or unknown. The rule
above hard-fails only undeliverable and lets risky and unknown through, so
you can decide downstream what to do with them — gate on score, or branch on a
sub_flags entry like disposable — instead of turning a borderline address into
a form error. Treat the JSON above as the shape, not the contract; the full field
list lives in the developer docs.
Putting the layers together to validate an email address in Laravel#
Compose all three in one form request, cheapest first, and let bail stop at the
first failure so the API rule only runs on addresses that already passed syntax
and the MX check:
namespace App\Http\Requests;
use App\Rules\DeliverableEmail;
use Illuminate\Foundation\Http\FormRequest;
class StoreSubscriberRequest extends FormRequest
{
public function rules(): array
{
return [
'email' => [
'bail',
'required',
'email:rfc,dns',
new DeliverableEmail(),
],
];
}
public function messages(): array
{
return [
'email.email' => 'Enter a valid email address on a real domain.',
];
}
}
bail is the whole trick. A malformed address or a dead domain fails locally and
never spends an API credit; only addresses worth the network round-trip reach the
SMTP layer. Type-hint the request in your controller and Laravel runs the pipeline
before your method body ever executes:
public function store(StoreSubscriberRequest $request)
{
// Every layer has passed — safe to persist.
$subscriber = Subscriber::create($request->validated());
return redirect()->route('subscribers.index');
}
The layering, not the framework, is what makes validation reliable — the same shape as the PHP version, with Laravel collapsing the first two layers into one rule. Run the fast synchronous pipeline at signup and reserve the slower, SMTP-confirmed pass for back-office work: the serverless signup guide shows the real-time pattern, and how to clean an email list covers the batch side.
Frequently asked questions#
Does Laravel's email rule check if the domain exists?#
Only if you ask it to. The plain email rule validates syntax with the
egulias/email-validator package (the rfc style). Add the dns style —
email:rfc,dns — and Laravel also confirms the domain resolves a mail route, so a
made-up or dead domain fails in the same pass. Neither style contacts the mailbox,
so a pass still means "deliverable-looking," not "deliverable."
What is the difference between email:rfc, email:filter, and email:dns?#
rfc validates against RFCs 5321/5322 via egulias and is Laravel's default.
filter runs PHP's filter_var($email, FILTER_VALIDATE_EMAIL) — the exact check
from the PHP guide — while filter_unicode allows some Unicode. dns adds a live
MX lookup, and spoof rejects deceptive homograph characters. You combine them
with commas, so email:rfc,dns is the practical syntax-plus-domain gate.
How do I verify a mailbox exists in Laravel?#
Not with a built-in rule — mailbox confirmation needs an SMTP conversation you
shouldn't run from your app server, because port 25 is widely blocked and the
result depends on your sending IP's reputation. Wrap a verification API in a
custom ValidationRule object, call it with the Http facade, read the result
envelope, and fail on undeliverable. Put it after email:rfc,dns with bail so
it only runs on addresses that already passed the local layers.
Will the dns rule slow down my form submissions?#
It can. The dns style performs a live DNS lookup on every validation, so a slow
or unreachable resolver stalls the request. It's fine for a single signup form,
but keep it off bulk imports, and don't stack it with a synchronous API call
unless you've set a timeout on the Http client — as the DeliverableEmail rule
above does with ->timeout(10).
Ready to add the mailbox layer? Drop an address into the
free email checker to watch a rule-approved address come
back undeliverable, then wire the same verdict into your ValidationRule with the
copy-paste examples in the API reference.