wesbos/burner-email-providers
A maintained list of disposable and burner email domains for blocking throwaway signups. Drop it into validation rules or your own checks to reject temporary addresses, with a simple dataset you can filter, cache, or extend.
There is no Composer package here — composer require wesbos/burner-email-providers will not resolve. This repo is a data source (a plain list of disposable-email domains, one per line, plus an npm wrapper). Your first job is deciding how that data gets into your app.
First steps:
emails.txt is the canonical list, and the npm entrypoint just re-exports it. Check the commit history: the value of this package is entirely "how recently was it updated," not code quality.curl -o database/data/burner-emails.txt \
https://raw.githubusercontent.com/wesbos/burner-email-providers/master/emails.txt
If you live in a JS build pipeline anyway, npm i burner-email-providers and copy the JSON export into resources/data/ during a build step. For a pure PHP/Laravel app, the raw file is simpler and has no node dependency.
Normalize it into a lookup structure once at boot (a singleton or a cached config array) — do not read the file per request.
First use case: block disposable addresses at registration. Wrap the list in a custom validation rule and attach it to your RegisterRequest:
// app/Rules/NotBurnerEmail.php
class NotBurnerEmail implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
[$local, $domain] = array_pad(explode('@', strtolower(trim($value)), 2), 2, null);
if ($domain && BurnerList::contains($domain)) {
$fail('Please use a permanent email address — disposable inboxes are not accepted.');
}
}
}
Then 'email' => ['required', 'email', new NotBurnerEmail].
Singleton set, flipped for O(1) lookup. Loading ~10k strings and calling in_array() per validation is wasteful and case-buggy. Flip once:
final class BurnerList
{
private static ?array $domains = null;
public static function contains(string $domain): bool
{
self::$domains ??= Cache::rememberForever('burner-domains', fn () =>
array_flip(array_filter(array_map(
fn ($d) => strtolower(trim($d)),
file(base_path('database/data/burner-emails.txt'), FILE_IGNORE_NEW_LINES)
)))
);
// also check the parent domain: [email protected] -> mailinator.com
do {
if (isset(self::$domains[$domain])) return true;
} while ($domain = Str::afterLast($domain, '.'));
return false;
}
}
Refresh as a scheduled command, not a runtime dependency. The list changes weekly. Ship a burner:update artisan command that pulls the raw file, validates it (non-empty, sane line count, all lines look like domains), and atomically replaces your snapshot. Schedule it monthly in routes/console.php, and commit the refreshed file so deploys are deterministic.
Schedule::command('burner:update')->monthly()->onOneServer();
// after writing the file:
Cache::forget('burner-domains');
Use it as a soft signal, not a hard gate, for existing accounts. A Registered event listener that tags the user (users.risk_flags or a DisposableEmail attribute) lets you throttle trials, skip welcome credits, or require manual review — without blocking signup flows you might regret blocking. Reserve hard rejection for free-tier signups and newsletter forms.
Layer the check. A domain list only catches lazy abuse. Pair it with a DNS/MX existence check and, for trial abuse specifically, a normalized-address comparison (strip +tags, compare the @ domain). Disposable providers rotate domains faster than any static list.
Test the plumbing, not the list. Don't assert on specific domains — the file changes. Assert that your rule rejects a domain you inject via a fixture and accepts gmail.com. For the update command, Http::fake() the raw GitHub response and assert the file and cache are refreshed.
Consider the mature alternative first. Propaganistas\LaravelDisposableEmail is a Composer-installable rule that wraps a comparable (larger, more actively synced) blocklist and gives you disposable_email validation out of the box. If all you need is "reject disposable at registration," use that and skip this package entirely. Reach for wesbos/burner-email-providers only when you want the raw data under your own control — e.g., you need it in a queued job, a scraper guard, or an internal admin filter, not just a validator.
composer.json. Everything above is code you write. Budget for that; it's not a five-minute composer require.str_contains($email, $domain). Substring matching against a domain list is a classic false-positive generator. Split on @ and compare the domain label exactly.mailinator.com; attackers send from anything.mailinator.com. Walk the domain labels upward as shown above.config:cache and Octane. If you put the list in a config file it gets baked into the cached config — a burner:update won't take effect until config:cache reruns. Keep the data in a file + cache store instead, and forget the cache key on refresh. Under Octane, the static $domains array persists per worker; make sure the refresh path clears it.config/burner-allowlist.php and check it before the blocklist rather than forking the vendored file.How can I help you explore Laravel packages today?