raul3k / disposable-email-blocker-core
Fast disposable/temporary email detection with PSL support, pattern matching, caching and whitelist
Package info
github.com/raul3k/disposable-email-blocker-core
pkg:composer/raul3k/disposable-email-blocker-core
Requires
- php: ^8.1
- ext-intl: *
- jeremykendall/php-domain-parser: ^6.4
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.0
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^10.0
- psr/cache: ^3.0
- psr/simple-cache: ^3.0
Suggests
- psr/cache: For PSR-6 cache adapter support
- psr/simple-cache: For PSR-16 cache adapter support
This package is auto-updated.
Last update: 2026-03-22 02:21:34 UTC
README
Fast disposable/temporary email detection with full Public Suffix List (PSL) support, pattern matching, caching, and whitelist capabilities.
Table of Contents
- Features
- Installation
- Quick Start
- Builder
- Detailed Check Results
- Batch Operations
- Pattern-Based Detection
- Whitelist Support
- Caching
- Custom Checkers
- Working with Sources
- Updating the Public Suffix List
- Domain Info
- Domain Normalization
- Framework Integration
- Development
- License
Features
- Fast O(1) lookups using hash-based domain checking
- Full PSL support - correctly handles subdomains and public suffixes
- IDN/Punycode support - international domain names are handled correctly
- Pattern matching - detect suspicious domain patterns via regex
- Whitelist support - allow specific domains to bypass checks
- Caching layer - PSR-6/PSR-16 compatible cache adapters
- Detailed results -
CheckResultwith matched checker info - Batch operations - efficiently check multiple emails at once
- Multiple checkers - file-based, callback-based, or chain multiple checkers
- Domain parsing -
DomainInfofor detailed domain analysis (PSL, subdomain, IDN) - Extensible sources - built-in sources + custom parsers for any format
- CLI tools -
bin/update-domainsto fetch and merge domain lists,bin/update-pslto update the Public Suffix List - Framework agnostic - use with any PHP project
Installation
composer require raul3k/disposable-email-blocker-core
Quick Start
use Raul3k\DisposableBlocker\Core\DisposableEmailChecker; $checker = DisposableEmailChecker::create(); // Check if email is disposable $checker->isDisposable('test@mailinator.com'); // true $checker->isDisposable('test@gmail.com'); // false // Safe version (returns false for invalid emails instead of throwing) $checker->isDisposableSafe('invalid-email'); // false // Check domain directly $checker->isDomainDisposable('tempmail.com'); // true
Builder
The fluent builder is the recommended way to compose checkers:
use Raul3k\DisposableBlocker\Core\DisposableEmailChecker; $checker = DisposableEmailChecker::builder() ->withBundledDomains() ->withPatternDetection() ->withWhitelist(['mycompany.com', 'partner.org']) ->withFileCache('/tmp/disposable-cache') ->build(); $checker->isDisposable('test@mailinator.com'); // true $checker->isDisposable('test@mycompany.com'); // false (whitelisted)
Available builder methods:
withBundledDomains()- use the bundled ~159k domain listwithDomainsFile(string $path)- use a custom domains filewithPatternDetection(?array $patterns = null)- enable regex pattern matchingwithChecker(CheckerInterface $checker)- add any custom checkerwithCallback(callable $callback)- add a callback-based checkerwithWhitelist(array $domains)- whitelist specific domainswithFileCache(string $directory, ?int $ttl = 3600)- enable file-based cachingwithCache(CacheInterface $cache, ?int $ttl = 3600)- use a custom cachewithNormalizer(DomainNormalizer $normalizer)- use a custom normalizer
Detailed Check Results
Get detailed information about the check result:
use Raul3k\DisposableBlocker\Core\DisposableEmailChecker; $checker = DisposableEmailChecker::create(); // Check an email $result = $checker->check('test@mailinator.com'); $result->isDisposable(); // true $result->isSafe(); // false $result->getDomain(); // 'mailinator.com' $result->getOriginalInput(); // 'test@mailinator.com' $result->getMatchedChecker(); // 'Raul3k\DisposableBlocker\Core\Checkers\FileChecker' $result->isWhitelisted(); // false $result->toArray(); // array representation $result->toJson(); // JSON string // Check a domain directly $result = $checker->checkDomain('mailinator.com'); $result->isDisposable(); // true // Safe versions (return safe result for invalid input instead of throwing) $result = $checker->checkSafe('invalid-email'); $result->isSafe(); // true
Batch Operations
Check multiple emails efficiently:
use Raul3k\DisposableBlocker\Core\DisposableEmailChecker; $checker = DisposableEmailChecker::create(); $emails = [ 'user1@gmail.com', 'user2@mailinator.com', 'user3@yahoo.com', ]; // Get boolean results $results = $checker->isDisposableBatch($emails); // ['user1@gmail.com' => false, 'user2@mailinator.com' => true, 'user3@yahoo.com' => false] // Get detailed results $results = $checker->checkBatch($emails); // ['user1@gmail.com' => CheckResult, 'user2@mailinator.com' => CheckResult, ...]
Pattern-Based Detection
Detect suspicious domain patterns using regex:
use Raul3k\DisposableBlocker\Core\DisposableEmailChecker; use Raul3k\DisposableBlocker\Core\Checkers\{ChainChecker, FileChecker, PatternChecker}; // Combine file-based checking with pattern matching $checker = DisposableEmailChecker::create( new ChainChecker([ new FileChecker(__DIR__ . '/domains.txt'), new PatternChecker(), // Uses default patterns ]) ); // Default patterns detect: // - temp*, disposable*, throwaway*, fake*, junk*, spam* // - 10minutemail, 5minmail, etc. // - guerrillamail, yopmail, mailinator // - Suspicious TLDs: .tk, .ml, .ga, .cf, .gq // Add custom patterns $patternChecker = new PatternChecker(); $patternChecker->addPattern('/^suspicious-/i');
Whitelist Support
Allow specific domains to bypass disposable checks:
use Raul3k\DisposableBlocker\Core\DisposableEmailChecker; use Raul3k\DisposableBlocker\Core\Checkers\{FileChecker, WhitelistChecker}; $innerChecker = new FileChecker(__DIR__ . '/domains.txt'); $whitelistChecker = new WhitelistChecker($innerChecker, [ 'company.com', // Allow company.com and all subdomains 'partner.org', ]); $checker = DisposableEmailChecker::create($whitelistChecker); // Even if mailinator.com is in the list, whitelist takes precedence $whitelistChecker->addToWhitelist('special-case.mailinator.com'); // Check whitelist status $whitelistChecker->isWhitelisted('company.com'); // true $whitelistChecker->isWhitelisted('sub.company.com'); // true (parent is whitelisted)
Caching
Add caching to improve performance for repeated checks:
use Raul3k\DisposableBlocker\Core\DisposableEmailChecker; use Raul3k\DisposableBlocker\Core\Checkers\{FileChecker, CachedChecker}; use Raul3k\DisposableBlocker\Core\Cache\{ArrayCache, FileCache}; // In-memory cache (single request) $cache = new ArrayCache(); // File-based cache (persistent) $cache = new FileCache('/path/to/cache/dir'); // Wrap any checker with caching $innerChecker = new FileChecker(__DIR__ . '/domains.txt'); $cachedChecker = new CachedChecker($innerChecker, $cache, ttl: 3600); $checker = DisposableEmailChecker::create($cachedChecker);
PSR-6/PSR-16 Cache Adapters
Use any PSR-compatible cache:
use Raul3k\DisposableBlocker\Core\Cache\{Psr6Adapter, Psr16Adapter}; // PSR-16 (SimpleCache) $cache = new Psr16Adapter($yourPsr16Cache); // PSR-6 (CacheItemPool) $cache = new Psr6Adapter($yourPsr6Pool); $cachedChecker = new CachedChecker($innerChecker, $cache);
Custom Checkers
Using a Callback (Redis, Database, API, etc.)
use Raul3k\DisposableBlocker\Core\DisposableEmailChecker; use Raul3k\DisposableBlocker\Core\Checkers\CallbackChecker; // Redis $checker = DisposableEmailChecker::create( new CallbackChecker(fn(string $domain) => $redis->sismember('disposable_domains', $domain)) ); // PDO $stmt = $pdo->prepare('SELECT 1 FROM disposable_domains WHERE domain = ? LIMIT 1'); $checker = DisposableEmailChecker::create( new CallbackChecker(function (string $domain) use ($stmt): bool { $stmt->execute([$domain]); return (bool) $stmt->fetchColumn(); }) ); // Eloquent (Laravel) $checker = DisposableEmailChecker::create( new CallbackChecker(fn(string $domain) => \App\Models\DisposableDomain::where('domain', $domain)->exists()) );
Implementing CheckerInterface
For reusable or complex checkers, implement the interface directly:
use Raul3k\DisposableBlocker\Core\Checkers\CheckerInterface; class PdoDatabaseChecker implements CheckerInterface { private \PDOStatement $stmt; public function __construct(\PDO $pdo, string $table = 'disposable_domains') { $this->stmt = $pdo->prepare( sprintf('SELECT 1 FROM %s WHERE domain = ? LIMIT 1', $table) ); } public function isDomainDisposable(string $normalizedDomain): bool { $this->stmt->execute([$normalizedDomain]); return (bool) $this->stmt->fetchColumn(); } } // Usage $checker = DisposableEmailChecker::create(new PdoDatabaseChecker($pdo));
use Illuminate\Database\Eloquent\Model; use Raul3k\DisposableBlocker\Core\Checkers\CheckerInterface; class EloquentDatabaseChecker implements CheckerInterface { /** @var class-string<Model> */ private string $model; /** @param class-string<Model> $model */ public function __construct(string $model = \App\Models\DisposableDomain::class) { $this->model = $model; } public function isDomainDisposable(string $normalizedDomain): bool { return $this->model::where('domain', $normalizedDomain)->exists(); } } // Usage $checker = DisposableEmailChecker::create(new EloquentDatabaseChecker());
Using a Custom File
use Raul3k\DisposableBlocker\Core\DisposableEmailChecker; use Raul3k\DisposableBlocker\Core\Checkers\FileChecker; $checker = DisposableEmailChecker::create( new FileChecker('/path/to/your/domains.txt') );
Chaining Multiple Checkers
use Raul3k\DisposableBlocker\Core\DisposableEmailChecker; use Raul3k\DisposableBlocker\Core\Checkers\{ChainChecker, FileChecker, PatternChecker, CallbackChecker}; $checker = DisposableEmailChecker::create( new ChainChecker([ new FileChecker('/path/to/domains.txt'), new PatternChecker(), new CallbackChecker(fn($domain) => $redis->sismember('extra_domains', $domain)), ]) ); // After checking, you can see which checker matched $result = $checker->check('test@tempmail.com'); $result->getMatchedChecker(); // 'Raul3k\DisposableBlocker\Core\Checkers\PatternChecker'
Working with Sources
Sources provide lists of disposable domains. The library includes several pre-configured sources.
Available Built-in Sources
| Source | Format | Size |
|---|---|---|
disposable-email-domains |
Text | ~5k |
burner-email-providers |
Text | ~27k |
mailchecker |
Text | ~56k |
ivolo-disposable |
JSON | ~122k |
fakefilter |
Text | ~10k |
Fetching from Sources
use Raul3k\DisposableBlocker\Core\Sources\SourceRegistry; $registry = new SourceRegistry(); // List available sources $sources = $registry->list(); // ['disposable-email-domains', 'burner-email-providers', 'mailchecker', ...] // Fetch domains from a source $source = $registry->get('disposable-email-domains'); foreach ($source->fetch() as $domain) { echo $domain . "\n"; }
Adding Custom Sources
use Raul3k\DisposableBlocker\Core\Sources\{SourceRegistry, UrlSource, FileSource}; use Raul3k\DisposableBlocker\Core\Parsers\{TextLineParser, JsonArrayParser}; $registry = new SourceRegistry(); // Remote text file (one domain per line) $registry->register(new UrlSource( url: 'https://example.com/domains.txt', name: 'my-text-source', parser: new TextLineParser() )); // Remote JSON array $registry->register(new UrlSource( url: 'https://example.com/domains.json', name: 'my-json-source', parser: new JsonArrayParser() )); // JSON with nested path $registry->register(new UrlSource( url: 'https://api.example.com/data.json', name: 'my-nested-json', parser: new JsonArrayParser('response.data.domains') )); // Local file $registry->register(new FileSource( path: '/path/to/local-domains.txt', name: 'my-local-source' ));
Updating the Bundled Domain List
Use the CLI tool to fetch domains from all sources and update the bundled list:
# Update from all sources ./bin/update-domains # Preview without writing ./bin/update-domains --dry-run # Fetch from specific sources only ./bin/update-domains --source=disposable-email-domains --source=mailchecker # Custom output path ./bin/update-domains --output=storage/domains.txt # Show detailed progress ./bin/update-domains --verbose # See all options ./bin/update-domains --help
You can customize sources via a disposable-blocker.php config file in your project root:
<?php use Raul3k\DisposableBlocker\Core\Sources\UrlSource; return [ 'sources' => [ new UrlSource( url: 'https://example.com/my-domains.txt', name: 'my-custom-source' ), ], 'exclude_sources' => ['fakefilter'], 'output_path' => __DIR__ . '/storage/disposable_domains.txt', ];
Updating the Public Suffix List
The library bundles a copy of the Public Suffix List for domain normalization. To update it:
# Update to the latest PSL ./bin/update-psl # Preview without writing ./bin/update-psl --dry-run # Show detailed progress ./bin/update-psl --verbose # Custom output path ./bin/update-psl --output=storage/public_suffix_list.dat
The script downloads from the canonical source, validates the content (size, structure, and parse checks), and writes the updated file.
A GitHub Actions workflow runs monthly to keep the bundled PSL current via automated PRs.
Domain Info
Parse and inspect domain details using the Public Suffix List:
use Raul3k\DisposableBlocker\Core\DomainInfo; $info = DomainInfo::parse('user@mail.example.co.uk'); $info->domain(); // 'example.co.uk' $info->subdomain(); // 'mail' $info->publicSuffix(); // 'co.uk' $info->secondLevelDomain(); // 'example' $info->host(); // 'mail.example.co.uk' $info->isIcann(); // true $info->isPrivate(); // false $info->isKnownSuffix(); // true $info->isValid(); // true // IDN support $info = DomainInfo::parse('пример.рф'); $info->ascii(); // 'xn--e1afmkfd.xn--p1ai' $info->unicode(); // 'пример.рф' $info->isIdn(); // true // Works with emails, domains, and URLs DomainInfo::parse('user@github.io')->isPrivate(); // true DomainInfo::parse('https://example.com/path')->domain(); // 'example.com'
Domain Normalization
The library normalizes domains using the Public Suffix List to correctly extract registrable domains:
use Raul3k\DisposableBlocker\Core\DomainNormalizer; $normalizer = new DomainNormalizer(); // Extract domain from email $normalizer->normalizeFromEmail('user@sub.example.com'); // 'example.com' // Normalize domain $normalizer->normalizeDomain('sub.example.com'); // 'example.com' $normalizer->normalizeDomain('sub.example.co.uk'); // 'example.co.uk' // Handle IDN $normalizer->normalizeDomain('пример.рф'); // 'xn--e1afmkfd.xn--p1ai'
Framework Integration
For Laravel integration, see:
Development
# Install dependencies composer install # Run tests composer test # Run tests with coverage composer test:coverage # Static analysis composer analyse # Code style check composer cs:check # Fix code style composer cs:fix # Run all quality checks composer quality
License
MIT License. See LICENSE for details.