arraypress / wp-email-utils
An immutable value object for working with email addresses in WordPress — parsing, validation, transformation, classification and analysis.
Requires
- php: >=8.3
Requires (Dev)
- phpcompatibility/phpcompatibility-wp: ^2.1
- phpunit/phpunit: ^12.0
- squizlabs/php_codesniffer: ^3.13.5
- wp-coding-standards/wpcs: ^3.4
This package is auto-updated.
Last update: 2026-08-25 15:46:30 UTC
README
An immutable value object for email addresses in WordPress: parsing, pattern matching, provider and institution detection, and typo correction.
Assumes WordPress. Validity is is_email()'s answer, not filter_var()'s —
the two disagree in both directions, and a library that silently switched
between them depending on what was loaded would give different answers to the
same question.
Installation
composer require arraypress/wp-email-utils
Requirements
- PHP 8.2+
- WordPress
Parsing
use ArrayPress\EmailUtils\Email; $email = Email::parse( 'David+newsletter@Gmail.com' ); if ( $email ) { $email->original(); // 'David+newsletter@Gmail.com' $email->normalized(); // 'david+newsletter@gmail.com' $email->local(); // 'david+newsletter' $email->domain(); // 'gmail.com' $email->tld(); // 'com' $email->base_local(); // 'david' $email->base_address(); // 'david@gmail.com' $email->subaddress(); // 'newsletter' }
parse() returns null for anything unusable, so there is no such thing as an
invalid Email.
$domain = Email::parse( $input )?->domain();
Detection
$email = Email::parse( 'admin@gmail.com' ); $email->is_role_based(); // true — admin@, info@, support@, … $email->is_common_provider(); // true — Gmail, Outlook, Yahoo, … $email->is_subaddressed(); // false $email->supports_subaddressing(); // true $email->has_mx(); // live DNS lookup — see the note below
Institutions
Email::parse( 'student@harvard.edu' )->is_educational(); // true Email::parse( 'clerk@irs.gov' )->is_government(); // true Email::parse( 'soldier@army.mil' )->is_military(); // true
Matched on the domain, not a substring: edu.com is a business, and granting a
tax exemption on a suffix match is a costly kind of wrong.
Typo correction
$email = Email::parse( 'user@gmial.com' ); $email->has_typo(); // true $email->suggested_domain(); // 'gmail.com' $email->suggested_email(); // 'user@gmail.com'
Only known misspellings of known providers. An unfamiliar company domain is left alone — suggesting a correction for a perfectly good domain is worse than suggesting nothing.
Pattern matching
$email = Email::parse( 'user@company.edu' ); $email->matches_pattern( 'user@company.edu' ); // exact address $email->matches_pattern( '@company.edu' ); // domain $email->matches_pattern( '.edu' ); // TLD $email->matches_pattern( 'company.edu' ); // domain and its subdomains $email->matches_any( [ '.edu', '@other.com' ] ); $email->matches_all( [ '.edu', 'company.edu' ] );
| Pattern | Example | Matches |
|---|---|---|
| Full address | user@test.com |
that address only |
| Domain | @company.com |
anything at that domain |
| TLD | .edu |
anything under that TLD |
| Partial domain | company.com |
@company.com and @sub.company.com |
Matching is literal about subaddresses: a rule for dave@example.com does not
match dave+tag@example.com. Use base_address() when you mean the mailbox
rather than the spelling.
Sanitising a list
Email::sanitize_pattern_list( "@test.com\n.edu\nnonsense\n" ); // [ '@test.com', '.edu' ] Email::sanitize_pattern_list( $raw, true ); // newline-separated string
Whatever survives sanitising is exactly what matches_* honours. A rule that
saves but never matches is worse than one refused at the point of typing.
Comparison
$a = Email::parse( 'david+test@gmail.com' ); $b = Email::parse( 'david+other@gmail.com' ); $a->equals( $b ); // false $a->equals_base( $b ); // true — both are david@gmail.com $a->same_domain( $b ); // true
Transformation
Immutable — every one returns a new instance.
$email->with_local( 'john' ); $email->with_domain( 'yahoo.com' ); $email->with_subaddress( 'shopping' ); $email->without_subaddress();
Output
(string) $email; // 'david@gmail.com' $email->get_formatted(); // 'david @ gmail.com' $email->to_array(); // everything parsed json_encode( $email ); // JsonSerializable
Settings helpers
Email::get_common_providers(); // [ 'gmail.com' => 'gmail.com', … ] Email::get_common_providers( true ); // [ [ 'value' => …, 'label' => … ], … ] Email::get_role_prefixes(); Email::get_role_prefixes( true );
A note on has_mx()
It performs a live DNS lookup on every call, with no caching. Fine in an admin screen or a one-off check; think twice before putting it on a checkout, where it becomes a blocking network round trip per submission.
What this library deliberately does not do
There is no spam scoring, no address hashing or anonymisation, and no TLD-to-country inference. The scoring was a second, weaker heuristic sitting next to a real one — EDD - Fraud Filter does that job with actual IP and email reputation behind it — and the pseudonymisation was untested privacy tooling, which is worse than none because it looks safe.
Development
composer install composer test # PHPUnit composer lint # WordPress coding standards composer format:check # Formatting only composer format # Apply formatting fixes
License
GPL-2.0-or-later.