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

sniffmail

v1.0.6

Published

Email validation library that detects disposable/burner email addresses and verifies mailbox existence

Readme

sniffmail

A comprehensive email validation library that detects disposable/burner email addresses and verifies mailbox existence via SMTP.

Features

  • Syntax Validation — Validates email format
  • Disposable Email Detection — Blocks burner/temporary email addresses using multiple sources:
    • GitHub disposable email blocklist (26,000+ domains)
    • Scraped domains from temp mail providers
    • Live-discovered domains from temp mail APIs
    • DeBounce API real-time detection
  • MX Record Lookup — Verifies the domain has mail servers
  • Deep SMTP Verification — Verifies the mailbox actually exists (powered by Sniffmail API)
  • Caching — In-memory or Redis caching to avoid redundant checks
  • Batch Processing — Validate multiple emails with concurrency control

Installation

npm install sniffmail

Quick Start

import { validateEmail } from 'sniffmail';

// Basic validation (syntax + disposable + MX check) — FREE, no API key needed
const result = await validateEmail('[email protected]');

if (!result.valid) {
  console.log(`Invalid: ${result.reason}`);
  // Possible reasons: 'invalid_syntax', 'disposable', 'no_mx_records'
}

Deep Mode (SMTP Verification)

Verify that a mailbox actually exists — catches fake emails like [email protected].

import { configure, validateEmail } from 'sniffmail';

// Get your free API key at https://sniffmail.io
configure({
  apiKey: process.env.SNIFFMAIL_API_KEY,
});

const result = await validateEmail('[email protected]', { deep: true });

if (result.smtp?.is_reachable === 'invalid') {
  console.log('Mailbox does not exist!');
}

Pricing

Up to 85% cheaper than ZeroBounce, NeverBounce, and Hunter.

| Tier | Verifications | Rate Limit | Price | |------|---------------|------------|-------| | Free | 500/month | 10 req/min | $0 | | Starter | 25,000/month | 60 req/min | $29/mo | | Pro | 100,000/month | 200 req/min | $79/mo | | Scale | 200,000/month | 200 req/min | $149/mo |

Also available: Credit Packs for pay-as-you-go usage (as low as $0.001/email).

Get your API key at https://sniffmail.io

API Reference

validateEmail(email, options?)

Validates a single email address.

const result = await validateEmail('[email protected]', {
  deep: false,        // Enable SMTP verification (default: false)
  checkMx: true,      // Check MX records (default: true)
  useDeBounce: true,  // Use DeBounce API (default: true)
  timeout: 5,         // DNS timeout in seconds (default: 5)
});

Returns: ValidationResult

{
  email: string;           // Normalized email address
  valid: boolean;          // Overall validity
  reason: ValidationReason | null;  // Why it's invalid
  disposable: boolean;     // Is it a disposable/burner email
  mx: boolean;             // Has valid MX records
  smtp: SmtpResult | null; // SMTP details (only with deep: true)
  cached: boolean;         // Was result from cache
}

Validation Reasons:

  • invalid_syntax — Email format is invalid
  • disposable — Domain is a known disposable email provider
  • no_mx_records — Domain has no mail servers
  • mailbox_not_found — Mailbox doesn't exist (deep mode)
  • mailbox_full — Mailbox is full (deep mode)
  • mailbox_disabled — Mailbox is disabled (deep mode)
  • catch_all — Domain accepts all emails (deep mode)
  • smtp_error — Could not connect to mail server (deep mode)

validateEmails(emails, options?)

Validates multiple emails with concurrency control.

import { validateEmails } from 'sniffmail';

const { results, summary } = await validateEmails(
  ['[email protected]', '[email protected]', '[email protected]'],
  {
    deep: true,
    concurrency: 5,  // Max parallel requests (default: 5)
  }
);

console.log(summary);
// { total: 3, valid: 2, invalid: 0, disposable: 1, unknown: 0 }

configure(options)

Configure global settings.

import { configure } from 'sniffmail';

configure({
  apiKey: 'sniff_xxx',  // Your Sniffmail API key
  cache: {
    enabled: true,
    ttl: {
      safe: 604800,     // 7 days
      invalid: 2592000, // 30 days
      risky: 86400,     // 1 day
      unknown: 0,       // Don't cache
    },
  },
});

isDisposableDomain(domain)

Quick synchronous check if a domain is disposable (no network calls, no API key needed).

import { isDisposableDomain } from 'sniffmail';

if (isDisposableDomain('tempmail.com')) {
  console.log('Blocked!');
}

Environment Variables

# Your Sniffmail API key (required for deep mode)
SNIFFMAIL_API_KEY=sniff_xxxxx

SMTP Result Details

When using deep: true, the smtp field contains:

{
  is_reachable: 'safe' | 'risky' | 'invalid' | 'unknown';
  can_connect: boolean;    // Could connect to SMTP server
  is_deliverable: boolean; // Mailbox accepts mail
  is_catch_all: boolean;   // Domain accepts all addresses
}

Reachability statuses:

  • safe — Email exists and is deliverable
  • risky — Email exists but might bounce (catch-all, full mailbox)
  • invalid — Email does not exist
  • unknown — Could not determine (timeout, blocked, etc.)

Usage Examples

Signup Form Validation

import { validateEmail } from 'sniffmail';

async function validateSignupEmail(email: string): Promise<string | null> {
  const result = await validateEmail(email);

  if (!result.valid) {
    switch (result.reason) {
      case 'invalid_syntax':
        return 'Please enter a valid email address';
      case 'disposable':
        return 'Disposable email addresses are not allowed';
      case 'no_mx_records':
        return 'This email domain does not exist';
      default:
        return 'Please enter a valid email address';
    }
  }

  return null; // Valid
}

Deep Verification for Critical Flows

import { configure, validateEmail } from 'sniffmail';

// Configure once at app startup
configure({
  apiKey: process.env.SNIFFMAIL_API_KEY,
});

async function verifyEmailForInvoice(email: string): Promise<boolean> {
  const result = await validateEmail(email, { deep: true });

  if (!result.valid) {
    console.log(`Email ${email} failed: ${result.reason}`);
    return false;
  }

  if (result.smtp?.is_reachable === 'risky') {
    console.log(`Warning: ${email} is risky (catch-all or full inbox)`);
  }

  return true;
}

Bulk Email List Cleaning

import { configure, validateEmails } from 'sniffmail';

configure({ apiKey: process.env.SNIFFMAIL_API_KEY });

async function cleanEmailList(emails: string[]): Promise<string[]> {
  const { results } = await validateEmails(emails, {
    deep: true,
    concurrency: 10,
  });

  return results
    .filter(r => r.valid && r.smtp?.is_reachable === 'safe')
    .map(r => r.email);
}

Error Handling

import {
  validateEmail,
  ApiKeyNotConfiguredError,
  SniffmailError
} from 'sniffmail';

try {
  const result = await validateEmail('[email protected]', { deep: true });
} catch (error) {
  if (error instanceof ApiKeyNotConfiguredError) {
    console.error('API key not set. Get one at https://sniffmail.io');
  } else if (error instanceof SniffmailError) {
    if (error.statusCode === 403) {
      console.error('Usage limit exceeded. Upgrade at https://sniffmail.io');
    } else if (error.statusCode === 429) {
      console.error('Rate limited. Slow down requests.');
    }
  }
}

Caching

Results are cached to avoid redundant verification:

| Status | Default TTL | Reason | |--------|-------------|--------| | safe | 7 days | Valid emails rarely change | | invalid | 30 days | Invalid emails almost never become valid | | risky | 1 day | Status may change | | unknown | 0 (no cache) | Retry on next request |

Redis Cache

import { configure, RedisCache } from 'sniffmail';
import Redis from 'ioredis';

const redis = new Redis('redis://localhost:6379');

configure({
  apiKey: process.env.SNIFFMAIL_API_KEY,
  cache: {
    store: new RedisCache(redis),
  },
});

License

MIT