arraypress/wp-email-utils

An immutable value object for working with email addresses in WordPress — parsing, validation, transformation, classification and analysis.

Maintainers

Package info

github.com/arraypress/wp-email-utils

pkg:composer/arraypress/wp-email-utils

Transparency log

Statistics

Installs: 138

Dependents: 1

Suggesters: 0

Stars: 1

Open Issues: 0

dev-main 2026-08-25 15:44 UTC

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.