A small, dependency-free PHP class for validating email addresses with sensible defaults: RFC syntax, length limits, allow-list of trusted providers, disposable-domain block-list, and DNS lookups.
Single file, single class, PHP 8.0+.
- Strict syntax check via
filter_var(..., FILTER_VALIDATE_EMAIL). - RFC 5321 length limits — 64 chars local part, 255 chars domain, 254 chars total.
- Allow-list of providers — defaults to common consumer mail hosts (Gmail, Outlook, iCloud, Proton, etc.). Pass
[]to allow any domain. - Disposable-domain block-list — rejects throw-away addresses (Mailinator, Guerrilla Mail, 10MinuteMail, etc.).
- DNS verification — checks
MX, falls back toA/AAAAper RFC 5321 §5. Toggleable for offline / unit tests. - Structured result — returns
valid,normalized, and machine-readableerrors[]codes. - Backward-compatible boolean helper (
isValid()). - No dependencies — pure PHP standard library.
- PHP 8.0 or newer (uses
mixed, constructor property promotion,str_contains). ext-filter(bundled with PHP).- Network access if
checkDnsis enabled (default).
Drop EmailValidator.php anywhere in your project and require it, or autoload it via Composer.
require_once __DIR__ . '/EmailValidator.php';Move the file to e.g. src/Support/EmailValidator.php, add a namespace at the top:
namespace App\Support;Then in composer.json:
{
"autoload": {
"psr-4": { "App\\": "src/" }
}
}Run composer dump-autoload and use App\Support\EmailValidator;.
require_once __DIR__ . '/EmailValidator.php';
$validator = new EmailValidator();
if ($validator->isValid('user@gmail.com')) {
echo 'Looks good!';
}$result = $validator->validate('foo@mailinator.com');
// $result =
// [
// 'valid' => false,
// 'normalized' => null,
// 'errors' => ['domain_disposable', 'domain_not_allowed'],
// ];When valid, normalized contains the trimmed, lower-cased-domain version of the address — the safe form to store in your database.
new EmailValidator(
?array $allowedProviders = null, // null = defaults, [] = allow any
array $disposableProviders = EmailValidator::DISPOSABLE_PROVIDERS,
bool $checkDns = true
);$validator = new EmailValidator(allowedProviders: []);$validator = new EmailValidator(
allowedProviders: ['acme.com', 'acme.co.uk']
);$validator = new EmailValidator(
disposableProviders: array_merge(
EmailValidator::DISPOSABLE_PROVIDERS,
['my-extra-bad-domain.tld']
)
);$validator = new EmailValidator(checkDns: false);validate() returns one or more of the following in errors[]:
| Code | Meaning |
|---|---|
email_not_string |
Input was not a string. |
email_empty |
Input was empty / whitespace only. |
email_contains_invalid_chars |
Characters were stripped by FILTER_SANITIZE_EMAIL. |
email_syntax_invalid |
Failed FILTER_VALIDATE_EMAIL. |
email_missing_at |
No @ found (defensive check). |
email_too_long |
Total length > 254 chars. |
local_part_length_invalid |
Local part empty or > 64 chars. |
domain_length_invalid |
Domain empty or > 255 chars. |
domain_missing_tld |
Domain has no dot. |
domain_disposable |
Domain matched the disposable block-list. |
domain_not_allowed |
Domain not in the allow-list (when one is configured). |
domain_no_dns_records |
No MX, A, or AAAA records for the domain. |
These are stable string codes — safe to use for translation keys or API responses.
require_once __DIR__ . '/EmailValidator.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$result = (new EmailValidator())->validate($_POST['email'] ?? '');
if (!$result['valid']) {
http_response_code(422);
exit('Invalid email: ' . implode(', ', $result['errors']));
}
register_user($result['normalized']);
}header('Content-Type: application/json');
$payload = json_decode(file_get_contents('php://input'), true);
echo json_encode(
(new EmailValidator())->validate($payload['email'] ?? null)
);The bottom of EmailValidator.php ships with a small CLI demo guarded by PHP_SAPI === 'cli', so it never runs when included from a web app.
php EmailValidator.php
php EmailValidator.php you@hotmail.com
php EmailValidator.php bad@@exampleExit code is 0 for valid, 1 for invalid — handy for shell scripts and CI.
checkdnsrr()requires outbound DNS. If your host blocks it, setcheckDns: falseor stub it in tests.- DNS existence does not guarantee the mailbox exists — only the domain is reachable. For deliverability checks, use a dedicated service (SMTP probe, Mailgun, Postmark, etc.).
FILTER_VALIDATE_EMAILis intentionally stricter than RFC 5322; some technically valid but exotic addresses (quoted local parts, IP-literal domains) may be rejected. This is usually what you want.- The default allow-list is opinionated. If you accept signups from any domain, pass
allowedProviders: [].
MIT — do whatever you want, no warranty.