Stop disposable and burner email addresses from polluting your signup flow. BurnerGuard ships a curated blocklist with zero runtime dependencies, works in any JavaScript environment, and gives you a clean async API to verify emails against it.
Disposable email detection sounds simple — just check a domain against a list. In practice, you end up solving the same problems every time:
- Raw domain lists leave you to handle parsing, deduplication, lookup optimization, email validation, and keeping the list current. Every project rebuilds this from scratch.
- Minimal wrappers give you a function but stop there — no allowlists to handle false positives, no batch operations, no way to bring your own data.
- Full email verification platforms pull in DNS and SMTP dependencies, add latency, and introduce complexity you may not need when all you want is a fast domain check.
BurnerGuard fills the gap: a fast, flexible, self-updating email checker that works offline, supports allowlists, handles batch operations, and ships with zero runtime dependencies.
A static blocklist catches known disposable domains, but it can't detect brand-new throwaway domains, catch-all configurations, or suspicious registration patterns.
- Fast lookups — Domains are stored in a
Setfor constant-time hash lookups. Subdomains are resolved by walking up the domain hierarchy, so even deep subdomains likea.b.c.throwaway.exampleresolve quickly. - Subdomain detection — If
throwaway.exampleis blocked,mail.throwaway.exampleis automatically caught too. Works at any depth. - Allowlist support — Explicitly permit domains that would otherwise be matched. The allowlist takes priority at every level of the domain hierarchy, so you can block
example.netbut allowlegit.example.net. - Flexible input — Pass a full email address or a bare domain to any method. BurnerGuard detects which one you gave it.
- Batch operations — Verify a list of emails, filter them into matched and clean buckets, or check whether any email in a set matches.
- Fully async — Every verification method returns a
Promise. The underlying work is synchronous — the async wrapper adds negligible overhead. - Bring your own data — Use the bundled blocklist out of the box, load from a local file, fetch from a URL, pass an inline array, or combine multiple sources. When custom sources are provided, the bundled list steps aside unless you explicitly keep it.
- Stays current — The bundled blocklist is updated automatically and published as new patch versions. Just run
npm updateto get the latest domains. - Lazy-loaded and memory-conscious — The bundled blocklist is loaded via dynamic
import()only whencreate()is called, not at module import time. If you're using custom sources exclusively, the bundled data is never loaded at all. - Universal — No Node.js built-ins in the core path. Works in Node.js, Deno, Bun, Angular, React, Vite, edge runtimes, and serverless functions. File-based sources dynamically import
node:fsonly when you opt into them. - Zero runtime dependencies — Nothing beyond platform built-ins.
- TypeScript-first — Full type definitions with a private-constructor factory pattern that guarantees every instance is fully initialized before you can use it.
Node.js 20 or later. Works in Deno, Bun, and edge runtimes with no additional configuration.
npm install @quarg/burnerguardyarn add @quarg/burnerguardpnpm add @quarg/burnerguardimport {BurnerGuard} from '@quarg/burnerguard';
const guard = await BurnerGuard.create();
const result = await guard.verify('user@throwaway.example');
// {
// isMatch: true,
// domain: 'throwaway.example',
// matchedOn: 'blocklist',
// isAllowlisted: false
// }verify() accepts a full email address or a bare domain. It returns a VerifyResult with everything you need to make a decision.
await guard.verify('user@throwaway.example'); // email address
await guard.verify('throwaway.example'); // bare domain — works too
await guard.verify('mail.throwaway.example'); // subdomain — caught automatically
await guard.verify('user@company.example'); // clean — { isMatch: false, ... }verifyBatch() checks multiple inputs and returns a result for each, in the same order.
const results = await guard.verifyBatch([
'alice@company.example',
'bob@throwaway.example',
'carol@another.example'
]);
// [{ isMatch: false, ... }, { isMatch: true, ... }, { isMatch: false, ... }]filter() splits your list into two buckets: matched (disposable) and clean.
const {matched, clean} = await guard.filter([
'alice@company.example',
'bob@throwaway.example',
'carol@another.example'
]);
// matched: ['bob@throwaway.example']
// clean: ['alice@company.example', 'carol@another.example']hasMatch() short-circuits on the first match — useful when you just need a yes/no answer for a batch.
await guard.hasMatch(['alice@company.example', 'bob@throwaway.example']); // true
await guard.hasMatch(['alice@company.example', 'carol@another.example']); // falseYou can add domains to the blocklist or allowlist after initialization. These changes apply to the current instance only.
await guard.block('sketchy.example');
(await guard.verify('user@sketchy.example')).isMatch; // true
await guard.allow('false-positive.example');
(await guard.verify('user@false-positive.example')).isMatch; // falseIf you know upfront which domains to add, pass them as options. These are merged with the bundled blocklist.
const guard = await BurnerGuard.create({
additionalBlockedDomains: ['sketchy.example'],
additionalAllowedDomains: ['legit-but-flagged.example']
});You can load domain lists from local files, remote URLs, or inline arrays. When custom sources are provided, the bundled blocklist is not loaded by default — pass useBundledBlocklist: true to keep it.
// From local files (Node.js only)
const guard = await BurnerGuard.create({
sources: [
{type: 'block', filePath: '/path/to/my-blocklist.txt'},
{type: 'allow', filePath: '/path/to/my-allowlist.txt'}
]
});
// From a URL (works everywhere — uses native fetch)
const guard = await BurnerGuard.create({
sources: [{
type: 'block',
url: 'https://example.com/my-blocklist.txt'
}]
});
// From an inline array
const guard = await BurnerGuard.create({
sources: [{type: 'block', list: ['spam.example', 'junk.example']}]
});
// Mix and match — bundled list + custom sources
const guard = await BurnerGuard.create({
useBundledBlocklist: true,
sources: [{type: 'block', list: ['my-extra-domain.example']}]
});Creates and initializes a new instance. Returns Promise<BurnerGuard>.
| Option | Type | Default | Description |
|---|---|---|---|
sources |
DomainListSource[] |
— | Domain list sources: file path, URL, or inline array. |
additionalBlockedDomains |
string[] |
— | Extra domains to add to the blocklist. |
additionalAllowedDomains |
string[] |
— | Extra domains to add to the allowlist. |
useBundledBlocklist |
boolean |
true* |
Whether to load the bundled blocklist. *Defaults to false when custom sources are provided. |
useBundledAllowlist |
boolean |
false |
Deprecated. Whether to load the bundled allowlist. The upstream source for this data has been removed. Use additionalAllowedDomains or a custom sources entry instead. Will be removed in a future major version. |
| Method | Returns | Description |
|---|---|---|
verify(emailOrDomain, options?) |
Promise<VerifyResult> |
Verify a single email or domain. |
verifyBatch(emailsOrDomains, options?) |
Promise<VerifyResult[]> |
Verify multiple inputs. Returns results in the same order. |
filter(emailsOrDomains, options?) |
Promise<FilterResult> |
Split inputs into {matched, clean}. |
hasMatch(emailsOrDomains, options?) |
Promise<boolean> |
Returns true if any input matches. Short-circuits on first match. |
block(domain) |
Promise<void> |
Add a domain to the blocklist at runtime. |
allow(domain) |
Promise<void> |
Add a domain to the allowlist at runtime. |
| Method | Returns | Description |
|---|---|---|
extractDomain(email) |
string | null |
Extract the domain portion from an email address. |
isValidEmail(email) |
boolean |
Check whether a string is a valid email address (syntax only). |
| Property | Type | Description |
|---|---|---|
blocklistSize |
number |
Number of domains currently in the blocklist. |
allowlistSize |
number |
Number of domains currently in the allowlist. |
Returned by verify().
{
isMatch: boolean; // true if the domain matched the blocklist
domain: string | null; // the extracted domain, or null for invalid input
matchedOn: string | null; // the signal that triggered the match (e.g., "blocklist"), or null
isAllowlisted: boolean; // true if the allowlist overrode a potential match
}Returned by filter().
{
matched: string[]; // inputs that matched the blocklist
clean: string[]; // inputs that did not match (or were allowlisted)
}BurnerGuard ships with a pre-processed copy of the disposable-email-domains community blocklist and allowlist. It works offline out of the box — no network calls needed.
The bundled data is updated automatically and published as new patch versions. Just run npm update to pick up new domains. No manual list management required.
To update the bundled data manually:
npm run update-blocklist
npm run build- Domain data sourced from the disposable-email-domains community blocklist.
- Email validation regex derived from Angular's form validation.
MIT — see LICENSE.