← Blog

How to validate an email address in PHP

PHP's filter_var and checkdnsrr cover syntax and MX with zero dependencies — here's exactly where they stop and what the API layer past them actually adds.

Contents

You typed a query into a search box that reads something like "validate email in PHP," and you are hoping the answer is not a 40-character regex someone pasted from a forum in 2009. Good news: it is not. PHP ships with the right tool built in, and for the syntax half of the job you need zero dependencies. The other half — does this address point at a domain that can actually receive mail, and is it a throwaway — takes one more built-in and, if you want it, one HTTP call. Let's do all three, honestly, and be clear about where each one stops.

Use filter_var, not a regex

The single most common mistake is writing your own pattern. Don't. Email syntax is defined by RFC 5322, the grammar is genuinely awful, and PHP already implements a pragmatic version of it in C:

$email = 'you@example.com';

if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
    // Not a syntactically valid address. Reject it.
    exit('Bad email format.');
}

FILTER_VALIDATE_EMAIL returns the address on success and false on failure, so a strict === false check is the safe read. This is the right built-in. It catches the missing @, the double dots, the trailing space, the whole class of typos that a naive regex either misses or over-rejects. If all you need is "did the user fill this field in with something shaped like an email," you are already done — ship it.

What filter_var does not tell you: whether the domain exists, whether it accepts mail, or whether you@example.com is a real inbox versus a plausible-looking string. Syntax and reachability are different questions.

Add an MX check with checkdnsrr

To find out whether the domain can receive mail at all, pull the domain off the address and ask DNS whether it publishes MX records:

$domain = substr(strrchr($email, '@'), 1);

if (checkdnsrr($domain, 'MX') === false) {
    // Domain publishes no MX records — it can't receive mail.
    exit('That domain does not accept email.');
}

checkdnsrr($domain, 'MX') returns true if any MX record exists and false otherwise. It performs a live DNS lookup, so it costs a network round trip — do it after the syntax check has already thrown out the garbage, and expect it to occasionally be slow or to time out on a flaky resolver. If you want the actual mail hosts rather than a boolean, getmxrr($domain, $hosts) fills an array by reference. Either way, you now know two real things: the address is well-formed, and its domain can, in principle, take mail.

Between filter_var and checkdnsrr you have covered syntax and MX with nothing but the standard library. For a contact form, that is a completely reasonable place to stop.

Where the built-ins stop

Here is what neither built-in can tell you. Is user@mailinator.com a disposable address that will evaporate in an hour? Is info@ a role account that goes to a shared mailbox nobody reads? Those judgments need a maintained list of throwaway domains and a list of role prefixes — data that goes stale and is not shipping inside PHP. You can hand-roll and hand-maintain both. Most people would rather not.

There is also the question of a live mailbox. Neither filter_var nor checkdnsrr opens an SMTP conversation to confirm the specific inbox exists, and honestly, you should be wary of anything that claims to — mailbox-level SMTP probing is unreliable, rate-limited, and often blocked outright.

The layer past: disposable and role, one contract

This is where an API earns its place — not by doing anything magic, but by folding the disposable list, the role list, and a live MX check into one response you get the same way from every language. Here is the call in plain PHP with cURL:

$ch = curl_init('https://api.boundstone.io/v1/verify/email');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer bs_live_YOUR_KEY',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS     => json_encode(['email' => $email]),
]);

$response = curl_exec($ch);
curl_close($ch);

$result = json_decode($response, true);

The response gives you valid_syntax, domain, mx_found, disposable, role_account, free_provider, and a checks object. So you can act on the parts the built-ins can't reach:

if ($result['disposable']) {
    exit('Disposable address — please use a permanent inbox.');
}

if ($result['role_account']) {
    // Warn, don't block: role accounts are real, just shared.
    $warn = 'That looks like a shared inbox (info@, sales@).';
}

One note so nothing surprises you: free_provider is returned as a flag — true for a gmail.com or outlook.com address — but it is not one of the checks the API counts as performed. It is context, not a verdict. Blocking free providers is almost always the wrong call.

Read the checks arrays before you trust valid

The reason to trust a "valid" from this API is that it tells you exactly what "valid" covered. For a well-formed address, the response reports:

"checks": {
  "performed":     ["syntax", "mx", "disposable_list", "role_list"],
  "not_performed": ["smtp_mailbox", "catch_all"]
}

Read not_performed as a feature, not fine print. smtp_mailbox and catch_all are listed because the API did not open an SMTP session to confirm the specific inbox exists, and did not detect whether the domain accepts every address (catch-all). Boundstone does not do those today, and says so in the response rather than implying a green checkmark means "this human reads mail here." A "valid" you can audit beats a "valid" you have to take on faith.

The short version

  • Validating email syntax in PHP? Use filter_var($email, FILTER_VALIDATE_EMAIL). Never a regex.
  • Want to know the domain can receive mail? Add checkdnsrr($domain, 'MX'). Two built-ins, zero dependencies — genuinely enough for many forms.
  • Need disposable and role detection too? That is the API's job — one contract across your stack, with a checks.not_performed list telling you the exact edges of the answer.

Kick the tires with no signup at the free email validator, see the same pattern in Python, or read the field-by-field contract in the docs.

Frequently asked questions

How do I validate an email address in PHP?

PHP's built-in filter_var($email, FILTER_VALIDATE_EMAIL) checks that an address is well-formed syntax, and you can layer on checkdnsrr($domain, 'MX') to confirm the domain publishes mail (MX) records. For a fuller check, an API like Boundstone runs syntax, MX lookup, disposable-domain and role-account screening in one call and returns valid_syntax, mx_found, disposable, role_account and free_provider. Every Boundstone response also lists checks.performed and checks.not_performed, so you know exactly what a result does and does not prove.

Does PHP's FILTER_VALIDATE_EMAIL confirm the email address actually exists?

No. FILTER_VALIDATE_EMAIL only checks that the string matches valid email syntax; it never contacts a mail server or confirms that anyone can receive mail at that address. You can go further by checking MX records and screening for disposable and role addresses, which is what Boundstone's email endpoint reports. Even then it does not perform SMTP mailbox verification or catch-all detection, so a 'valid' result means deliverable-shaped, not that a specific inbox is real.

How do I check whether an email domain can actually receive mail in PHP?

Look up the domain's MX records with checkdnsrr($domain, 'MX') or getmxrr(), which tells you whether the domain publishes a mail server. Boundstone's email verify does this and returns mx_found alongside disposable, role_account and free_provider flags, with each response explicitly listing checks.performed (syntax, mx, disposable_list, role_list) and checks.not_performed (smtp_mailbox, catch_all). A present MX record means the domain is set up to accept mail, but it cannot prove the individual mailbox exists, so Boundstone reports that honestly rather than guessing.

Thomas Tsui

Founder of Boundstone — building phone, email and IP validation you can actually verify.

One honest API for email, phone and IP — every response lists what it checked and what it didn't claim to. Free tier: 250 credits/month, no card, credits never expire.

More from Boundstone — API documentation · Benchmark methodology · The benchmark series · Buyer's checklist