npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

tempmail-checker

v1.1.1

Published

Fast, isomorphic disposable/temporary email detector with Bloom filter + HashSet pipeline, subdomain detection, and auto-updating domain lists

Downloads

189

Readme

tempmail-checker

npm version CI License: MIT TypeScript Node.js

Fast, isomorphic disposable email detector with a 2-tier Bloom Filter → HashSet validation pipeline, deep subdomain detection, and auto-updating domain lists.


✨ Features

  • 🚀 O(1) Lookup — Bloom filter pre-check eliminates ~95% of queries instantly
  • 🎯 Zero False Negatives — HashSet confirms every Bloom filter positive
  • 🌐 Isomorphic — Works in Node.js and browsers
  • 🔍 Subdomain Detection — Catches x.y.tempmail.com even if only tempmail.com is listed
  • 📋 5,300+ Domains — Sourced from disposable-email-domains
  • 🔄 Auto-Update — Optional runtime refresh from GitHub
  • 📦 Tiny Bloom Filter — ~6KB pre-computed filter, ~93KB total package
  • 🧩 Dual API — Simple functions + powerful class-based API
  • ✅ Custom Lists — Add your own blocklist/allowlist
  • 📝 Rich Results — Detailed validation result objects, not just booleans
  • 🪵 Configurable Logging — Debug, info, warn, error levels with custom handlers
  • 🔌 Framework Middleware — Ready-to-use plugins for Express and Fastify

📦 Installation

npm install tempmail-checker
yarn add tempmail-checker
pnpm add tempmail-checker

🚀 Quick Start

Simple Function API

import { isDisposable, isTemp } from 'tempmail-checker';

// Rich result object
const result = isDisposable('[email protected]');
console.log(result);
// {
//   disposable: true,
//   email: '[email protected]',
//   domain: 'mailinator.com',
//   reason: 'blocklist',
//   matchedDomain: 'mailinator.com'
// }

// Quick boolean check
if (isTemp('[email protected]')) {
  console.log('Disposable email detected!');
}

Class-based API

import { EmailValidator } from 'tempmail-checker';

const validator = new EmailValidator({
  customBlocklist: ['my-spam-domain.com'],
  customAllowlist: ['legit-but-flagged.com'],
  autoUpdate: false,
});

// Single check
const result = validator.check('[email protected]');

// Batch check
const results = validator.checkMany([
  '[email protected]',
  '[email protected]',
  '[email protected]',
]);

// Get stats
console.log(validator.stats());
// {
//   totalDomains: 5363,
//   bloomFilterSizeBytes: 6424,
//   hashSetSize: 5363,
//   customBlocklistSize: 1,
//   allowlistSize: 1,
//   lastUpdated: null
// }

// Clean up when done
validator.destroy();

🏗️ Architecture

  "[email protected]"
         │
         ▼
  ┌──────────────┐
  │  Parse Email  │──── invalid ──▶ { disposable: false, reason: 'invalid_email' }
  │  + Extract    │
  │  Subdomains   │
  └──────┬───────┘
         │
    ┌────▼─────┐    ┌───────────┐
    │ ALLOWLIST │───▶│ ✅ VALID  │  (highest priority)
    └────┬─────┘    └───────────┘
         │
    ┌────▼──────────┐    ┌──────────────┐
    │ CUSTOM BLOCK  │───▶│ 🚫 BLOCKED  │
    └────┬──────────┘    └──────────────┘
         │
         ▼ for each domain level
  ┌──────────────┐
  │ BLOOM FILTER │──── "definitely NOT" ──▶ skip, try next level
  │  (Layer 1)   │
  │  ~6KB, O(1)  │──── "MAYBE yes" ───┐
  └──────────────┘                     │
                                       ▼
                                ┌──────────────┐
                                │   HASH SET   │──── match ──▶ 🚫 DISPOSABLE
                                │  (Layer 2)   │
                                │   O(1)       │──── no match ──▶ next level (bloom FP)
                                └──────────────┘

Why 2 tiers?

| Aspect | Bloom Only | HashSet Only | Bloom + HashSet | | ------------------ | ------------- | ------------ | ------------------- | | Memory | ~6 KB | ~350 KB | ~356 KB | | False Positives | ~1% | None | None | | False Negatives | Never | Never | Never | | Speed (not found) | Fastest | Fast | Fastest ⚡ |

The Bloom filter acts as a fast rejection gate — if it says "definitely not disposable", we skip the HashSet entirely. Only uncertain cases (~1%) proceed to exact matching.


📖 API Reference

isDisposable(email: string): ValidationResult

Quick check using a singleton validator with default settings.

isTemp(email: string): boolean

Returns true if the email is disposable. Convenience wrapper.

new EmailValidator(options?)

| Option | Type | Default | Description | | ------------------ | ---------- | ------------ | -------------------------------------- | | customBlocklist | string[] | [] | Additional domains to block | | customAllowlist | string[] | [] | Domains to always allow | | autoUpdate | boolean | false | Enable periodic remote refresh | | updateInterval | number | 86400000 | Refresh interval in ms (default: 24h) | | updateUrl | string | GitHub URL | Custom URL for domain list | | falsePositiveRate| number | 0.01 | Bloom filter FPR (0.01 = 1%) | | logging | object | undefined | Logging config (see below) |

validator.check(email): ValidationResult

interface ValidationResult {
  disposable: boolean;
  email: string;
  domain: string;
  reason:
    | 'blocklist'         // Matched main disposable list
    | 'subdomain_match'   // Parent domain matched
    | 'custom_blocklist'  // Matched user's custom blocklist
    | 'allowlist'         // Overridden by custom allowlist
    | 'not_found'         // Not in any blocklist
    | 'invalid_email';    // Malformed email address
  matchedDomain?: string; // The domain that triggered the match
}

validator.checkMany(emails): ValidationResult[]

Batch check multiple emails.

validator.refresh(): Promise<void>

Manually fetch the latest domain list and rebuild the filter.

validator.stats(): ValidatorStats

Returns statistics about the validator's current state.

validator.destroy(): void

Clean up resources (stops auto-update timer).


🔍 Subdomain Detection

Subdomain bypass is a common attack vector. tempmail-checker walks the entire domain hierarchy:

import { isDisposable } from 'tempmail-checker';

// All of these are caught:
isDisposable('[email protected]');           // ✅ blocklist
isDisposable('[email protected]');       // ✅ subdomain_match
isDisposable('[email protected]');      // ✅ subdomain_match

Multi-part TLDs (.co.uk, .com.au) are handled correctly — we never falsely match bare TLDs.


🔄 Auto-Updating

const validator = new EmailValidator({
  autoUpdate: true,
  updateInterval: 12 * 60 * 60 * 1000, // every 12 hours
});

// Or manually refresh
await validator.refresh();

// Don't forget to clean up
validator.destroy();

The domain list is sourced from disposable-email-domains, which is actively maintained.


🧰 Advanced Usage

Custom Blocklist & Allowlist

const validator = new EmailValidator({
  customBlocklist: ['internal-spam.com', 'known-bad.org'],
  customAllowlist: ['mailinator.com'], // Override if you need to
});

Priority order: Allowlist > Custom Blocklist > Default Blocklist

Direct Bloom Filter Access

import { BloomFilter } from 'tempmail-checker';

const filter = BloomFilter.create(1000, 0.01);
filter.add('example.com');
filter.test('example.com'); // true (definitely or maybe)
filter.test('other.com');   // false (definitely not)

Utility Functions

import { parseEmail, getDomainLevels } from 'tempmail-checker';

parseEmail('[email protected]');
// { local: 'user', domain: 'mail.example.com' }

getDomainLevels('mail.example.com');
// ['mail.example.com', 'example.com']

🪵 Configurable Logging & Debugging

Logging is silent by default — opt-in via options or environment variable.

Via Options

import { EmailValidator } from 'tempmail-checker';

const validator = new EmailValidator({
  logging: {
    level: 'debug', // 'debug' | 'info' | 'warn' | 'error' | 'silent'
    prefix: '[my-app]', // custom prefix (default: '[tempmail-checker]')
  },
});

validator.check('[email protected]');
// [my-app] DEBUG Checking email { email: '[email protected]', domain: 'mailinator.com', levels: 1 }
// [my-app] INFO Disposable email detected { email: '[email protected]', matchedDomain: 'mailinator.com', reason: 'blocklist' }

Via Environment Variable

TEMPMAIL_LOG_LEVEL=debug node your-app.js

Custom Log Handler

import { EmailValidator } from 'tempmail-checker';

const validator = new EmailValidator({
  logging: {
    level: 'info',
    handler: (level, message, meta) => {
      // Send to your logging system (Winston, Pino, etc.)
      myLogger[level](message, meta);
    },
  },
});

Standalone Logger

import { createLogger } from 'tempmail-checker';

const logger = createLogger({ level: 'debug' });
logger.debug('Custom message', { key: 'value' });
logger.setLevel('warn'); // change at runtime

🔌 Framework Middleware

Express

import express from 'express';
import { createExpressMiddleware } from 'tempmail-checker/middleware/express';

const app = express();
app.use(express.json());

// Block disposable emails on registration
app.post('/register',
  createExpressMiddleware({
    emailFields: ['body.email'],      // where to find the email (dot notation)
    blockDisposable: true,            // block with 422 (default)
    errorMessage: 'Please use a real email address.',
    errorStatusCode: 422,
  }),
  (req, res) => {
    res.json({ message: 'Registered!' });
  }
);

// Or just attach results without blocking
app.post('/check',
  createExpressMiddleware({ blockDisposable: false }),
  (req, res) => {
    res.json({ validation: req.emailValidation });
  }
);

Express Middleware Options

| Option | Type | Default | Description | | ------------------ | ---------- | ---------------------------------------------- | ------------------------------------------ | | emailFields | string[] | ['body.email'] | Dot-notation paths to email fields | | blockDisposable | boolean | true | Block request if disposable email found | | errorMessage | string | 'Disposable email addresses are not allowed.'| Error message in the response | | errorStatusCode | number | 422 | HTTP status code for blocked requests | | All ValidatorOptions are also supported (customBlocklist, customAllowlist, logging, etc.) |

Fastify

import Fastify from 'fastify';
import { fastifyTempmail } from 'tempmail-checker/middleware/fastify';

const app = Fastify();

app.register(fastifyTempmail, {
  emailFields: ['body.email'],
  blockDisposable: true,
  errorMessage: 'Disposable emails not allowed.',
});

app.post('/register', async (req, reply) => {
  // If we reach here, the email is not disposable
  return { message: 'Registered!' };
});

// Access validation results
app.post('/check', {
  preHandler: [],  // plugin hooks run automatically
}, async (req) => {
  return { validation: req.emailValidation };
});

📊 Performance

✅ 10,000 email checks: < 50ms
✅ Bloom filter lookup: O(1)
✅ HashSet lookup: O(1)
✅ Memory footprint: ~356 KB
✅ Package size: ~93 KB (minified)

🤝 Contributing

Contributions are welcome! See CONTRIBUTING.md for guidelines.

# Clone the repo
git clone https://github.com/Eahtasham/tempmail-checker.git
cd tempmail-checker

# Install dependencies
npm install

# Generate domain data
npm run build:data

# Run tests
npm test

# Build
npm run build

📃 License

MIT © tempmail-checker contributors


🙏 Credits