You've got a signup form that looks fine, the email field passes, and the first campaign still comes back with bounces you can't ignore. That's the usual moment people start searching for email validation in PHP, and it's usually because a single syntax check didn't solve the underlying issue.
The fix isn't another clever regex in isolation. It's a layered pipeline, syntax first, then infrastructure checks, then classification signals, and only then a decision about whether to hand the address off to a verification service. PHP gives you a solid starting point, but it doesn't give you mailbox certainty on its own, and that gap is where most bad addresses slip through.
Table of Contents
- Why a Valid-Looking Email Still Bounces
- Built-In Syntax Checks with filtervar and Regex
- Domain and MX Lookups with checkdnsrr
- Optional SMTP Mailbox Probing and Its Risks
- Detecting Disposable Domains and Role Addresses
- Putting the Layers Together in a Signup Pipeline
- When to Hand the Job to a Verification Service
Why a Valid-Looking Email Still Bounces
A marketer imports a clean-looking signup list, sends a welcome series, and the first campaign lands with a thud because some addresses never had a reachable mailbox in the first place. The string looked fine. The domain might even have looked respectable. None of that guarantees delivery.
That's why PHP email validation gets misunderstood so often. Format validity only means the address follows the rules closely enough to look like an email string, while deliverability means a real mail system can accept mail for it. The PHP manual documents filter_var() with FILTER_VALIDATE_EMAIL as the native starting point for syntax checks, and that's still the right first gate, but it only checks the structure of the address, not mailbox existence. The same distinction is now emphasized in layered validation guidance, which recommends syntax, DNS checks, and then a verification step when reachability matters, because a valid-looking string can still bounce if the domain has no working mail infrastructure. See the practical framing in recent email service issues, where service disruptions make a strong case for checking more than just the string itself.
The failure mode you're really fixing
The common mistake is treating one rule as the whole system. A signup form accepts name@domain.tld, the database stores it, and only later does the bounce monitor reveal that the address was never useful for sending.
Practical rule: if the address affects sender reputation, list quality, or revenue, format validation alone is too shallow.
The layered model solves that by separating questions that look similar but aren't. Is the string well-formed? Does the domain exist? Does the mailbox answer? Is it disposable? Is it a role account that should be tagged, not rejected? Once you ask those questions separately, the implementation gets clearer and the false confidence drops fast.
Built-In Syntax Checks with filter_var and Regex
PHP gives you two common syntax gates, filter_var() and a custom regular expression. They solve the same first problem, “does this look like an email address?”, but they make different trade-offs around readability and strictness. For most apps, filter_var($email, FILTER_VALIDATE_EMAIL) should come first because it's the native validator and it's easier to maintain than a hand-built pattern.
<?php
$email = trim($_POST['email'] ?? '');
if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
echo "Invalid email format.";
exit;
}
echo "Email format looks valid.";
A clean regex can work too, but it should stay readable and anchored. The guidance that matters is simple, keep it anchored with ^ and $, use non-capturing groups where they help, and don't make the pattern so strict that you block legitimate addresses. That warning matters because regex is a syntax test, not a delivery test, and turning it into a deliverability proxy creates the wrong confidence.
<?php
$email = trim($_POST['email'] ?? '');
$pattern = '/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/';
if (!preg_match($pattern, $email)) {
echo "Invalid email format.";
exit;
}
echo "Email format looks valid.";
filter_var vs Custom Regex for Syntax Checks
| Criterion | filter_var | Custom Regex |
|---|---|---|
| Readability | Clear and familiar | Depends on who wrote it |
| Maintenance | Low effort | Easy to drift over time |
| Edge-case control | Fixed PHP rule set | You choose the rule set |
| Risk of false rejects | Moderate for unusual but valid inputs | Often higher if over-strict |
| Best use | Default first-line syntax check | Custom policy enforcement |
filter_var() will reject some unusual but technically valid formats, including cases your audience may never use, and that's fine for many consumer forms. It also won't tell you whether the mailbox exists, whether the domain is active, or whether the address is disposable. If you need a deeper PHP-focused reference on formatting trade-offs, the internal guide at email address formatting is worth keeping nearby.
The practical move is to treat syntax checks as a gatekeeper, not a verdict. If the address passes, you've only earned the right to do the next check.
Domain and MX Lookups with checkdnsrr
Once the string is well-formed, the next question is whether the destination can receive mail at all. A syntactically valid address with a dead domain is still useless for sending, and that's where PHP's DNS helpers come in. checkdnsrr() gives you a lightweight existence check, while getmxrr() helps you inspect whether the domain has mail routing records.

A safe DNS check pattern
A raw DNS lookup can stall a signup flow if the resolver is slow, so time-bounding matters. I prefer a short guard around the lookup rather than letting a request hang while PHP waits on the network.
<?php
function domainLooksReachable(string $email): bool
{
$parts = explode('@', $email);
if (count($parts) !== 2) {
return false;
}
$domain = $parts[1];
$started = microtime(true);
$hasMx = function_exists('getmxrr') && getmxrr($domain, $mxHosts);
if ($hasMx) {
return true;
}
if ((microtime(true) - $started) > 0.5) {
return false;
}
return checkdnsrr($domain, 'A');
}
That pattern keeps the logic simple. If MX records exist, the domain is at least trying to receive mail. If MX records don't exist but A records do, some systems may still accept mail as a fallback. If neither exists, the address should fail with a clear message tied to the domain, not the local part.
A valid-looking mailbox at a dead domain is still a dead lead.
This layer is about destination infrastructure, not mailbox identity. It's useful because it removes obvious junk before you spend time on deeper verification. It isn't enough on its own, but it's a strong and cheap checkpoint.
Optional SMTP Mailbox Probing and Its Risks
SMTP probing is the closest thing you can do in PHP to ask, “will this mailbox accept mail?” The basic flow opens a connection to the mail host, speaks SMTP, and checks whether the server accepts the recipient during the RCPT TO step. The standard sequence is EHLO, MAIL FROM, RCPT TO, then QUIT, and a 250 response can mean the address is accepted by that server.
<?php
function smtpProbe(string $host, string $recipient): bool
{
$fp = fsockopen($host, 25, $errno, $errstr, 5);
if (!$fp) {
return false;
}
fgets($fp);
fwrite($fp, "EHLO example.com\r\n");
fgets($fp);
fwrite($fp, "MAIL FROM:<probe@example.com>\r\n");
fgets($fp);
fwrite($fp, "RCPT TO:<{$recipient}>\r\n");
$response = fgets($fp);
fwrite($fp, "QUIT\r\n");
fclose($fp);
return str_starts_with($response, '250');
}
Why the result is useful, and why it still lies
A positive response matters. It means the server accepted the recipient at that moment. A negative or temporary failure is harder to interpret, because many large providers intentionally blur the response to stop address enumeration. That means a 4xx reply doesn't reliably prove the mailbox is dead, and a refusal can reflect policy rather than reality.
That's the trade-off. Probing can give you better signal than DNS alone, but it's also slower, noisier, and more fragile than the earlier layers. It can create reputation and policy issues if you probe servers you don't send through, and it can add latency to a live form that users expect to feel instant.
Use it only when the address is high-value and only after earlier checks have passed. For most signups, it belongs behind your own mail flow or inside an asynchronous review process, not in the first request that a user submits.
Detecting Disposable Domains and Role Addresses
Some addresses are valid enough to pass syntax and DNS, but they still aren't useful for your business. Disposable domains belong in that category, and so do role accounts like info@, support@, and admin@ when your goal is one-to-one outreach rather than generic inbox contact. They're not the same problem, so they shouldn't get the same treatment.

Blocklist and classification are different decisions
A disposable check is usually a blocklist problem. If the domain appears on your list of throwaway providers, you can reject it or send it to review. A role-address check is more often a product decision, because support@ is a valid mailbox, it's just often the wrong one for first-touch marketing or sales.
- Disposable domain scan: compare the domain against a maintained blocklist of throwaway providers, and treat matches as high-risk.
-
Role address filter: flag generic inboxes like
info@,support@, andadmin@, then decide whether to reject, warn, or tag them.
The internal guide at what is a disposable email address is useful if you need a clean explanation for product or support teams.
Why international addresses deserve special care
Many PHP implementations get too narrow. The open-source egulias/EmailValidator explicitly supports RFCs 5321, 5322, 6530, 6531, 6532, and 1035, plus spoof-checking for multi-UTF-8 character issues, which indicates the problem is broader than ASCII-only syntax. If your audience includes global users, over-strict local rules can reject legitimate addresses because they don't fit a narrow pattern.
That's the practical choice. A homegrown blocklist can work for a small, stable list of disposable domains, but a library with internationalized support reduces the chance that your validation layer shuts out real users in other markets.
Putting the Layers Together in a Signup Pipeline
A useful signup validator doesn't ask one giant question. It asks smaller ones in the right order so each layer filters out a different class of bad input. The cheapest checks come first, the slower ones come later, and anything optional should only run after the earlier gates pass.

A short-circuit flow that stays fast
<?php
function validateSignupEmail(string $email): array
{
$email = trim($email);
if ($email === '' || filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
return ['ok' => false, 'reason' => 'format'];
}
if (!domainLooksReachable($email)) {
return ['ok' => false, 'reason' => 'domain'];
}
if (preg_match('/^(info|support|admin)@/i', $email)) {
return ['ok' => false, 'reason' => 'role'];
}
if (preg_match('/@(mailinator\.com|guerrillamail\.com)$/i', $email)) {
return ['ok' => false, 'reason' => 'disposable'];
}
return ['ok' => true, 'reason' => 'accepted'];
}
That order matters. You don't want to run a DNS check on obviously broken syntax, and you don't want to probe SMTP on an address that already failed a cheaper gate. Log the verdict for later review, because the pattern of failures tells you where your audience is drifting and which checks are too strict for your product.
The internal guide at check if email is valid fits well alongside this pipeline if you want a second view on implementation order. The key is not complexity for its own sake. The key is to stop doing expensive work on junk input.
When to Hand the Job to a Verification Service
DIY validation has a ceiling. You can maintain a syntax gate, add DNS checks, and keep a small disposable list fresh, but the work starts to pile up once your traffic grows or your sending reputation starts affecting revenue. SMTP probing helps, yet it still can't reliably defeat providers that mask responses, and it adds latency that users feel immediately on a live form.
That's where a dedicated verification service starts making sense. A service like CleanMyList checks syntax, DNS, SMTP mailbox existence, catch-all behavior, disposable providers, role accounts, historical bounce reputation, and a final send/skip recommendation, and it gives a plain-English reason for each verdict. It also offers a one-line widget that blocks typos and fake signups before bad data enters the system. For teams that need a practical reference on deliverability checks, check email deliverability is a useful external comparison point for the general problem space.
A practical decision rule
If you're running a smaller signup flow on a single domain, the layered PHP pipeline is usually enough. If you're handling larger volumes, re-using old lists, or treating sender reputation as a revenue input, the maintenance burden becomes real fast. Disposable lists age out, role-account policy changes, and mailbox behavior shifts in ways a single app server can't track well by itself.
That's also where bounce history matters. A local app can check a single address at a moment in time, but it can't build the same feedback loop from repeated sends, skips, and bounces that a dedicated verifier can use to refine the verdict.
A few edge cases still come up often:
- Plus addressing: keep it, don't reject it just because there's a tag after the local part.
- Catch-all domains: treat them as lower-confidence, not as automatically safe.
- Every submit versus signup only: validate at signup, then re-check before a major send if the list has aged.
- Disposable blocklists: refresh them regularly or use a service that keeps them current for you.
If your workflow needs that extra layer of certainty, CleanMyList is built for bulk verification before send, and it's also usable as a signup-time filter when you want bad addresses blocked early. Visit CleanMyList and use the layered approach here to decide whether you should keep the logic in PHP or move the higher-risk cases into a dedicated verifier.
