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

mailcapture

v1.0.2

Published

Official JavaScript/TypeScript SDK for the MailCapture email testing API

Downloads

47

Readme

mailcapture

Official JavaScript/TypeScript SDK for MailCapture — a real email capture API for integration testing OTP codes, verification links, and other transactional emails.

MailCapture captures emails sent by your application during testing. Point your app at a MailCapture address, trigger your email flow, then use this SDK to retrieve and assert on what arrived — subject lines, body text, OTP codes, and more. No mock SMTP server, no inbox polling, no flaky waits.

A MailCapture account is required — free and paid plans are available. Sign up at mailcapture.app.

Installation

npm install mailcapture
# or
yarn add mailcapture
# or
pnpm add mailcapture

Requires Node.js 18+ (uses native fetch).

Quick start

import MailCapture from 'mailcapture'

const mc = new MailCapture(process.env.MAILCAPTURE_API_KEY)

// Get your capture address template
const { username } = await mc.ping()

// Send an email to [email protected], then:
const email = await mc.waitFor('signup', { timeout: 15_000 })

console.log(email.subject)   // "Welcome to Acme!"
console.log(email.otp)       // "123456" — extracted automatically

The pattern for integration tests

  1. Clear the inbox before each test
  2. Trigger the action that sends the email (register, reset password, etc.)
  3. Wait for the email — waitFor holds the connection open and returns the instant it arrives
  4. Assert on subject, OTP, body, links
  5. Clean up after
// vitest / jest example
describe('signup flow', () => {
  let mc: MailCapture
  let inbox: Inbox

  beforeAll(async () => {
    mc = new MailCapture(process.env.MAILCAPTURE_API_KEY)
    await mc.ping()                    // validates key, caches username
    inbox = mc.inbox('signup')
  })

  beforeEach(() => inbox.clear())      // clean slate for every test

  it('sends a 6-digit OTP on signup', async () => {
    await registerUser(inbox.address)  // e.g. "[email protected]"

    const email = await inbox.waitFor({ timeout: 10_000 })

    expect(email.subject).toBe('Verify your email')
    expect(email.otp).toMatch(/^\d{6}$/)
  })
})

API reference

new MailCapture(apiKey, options?)

const mc = new MailCapture('mc_...')

// With options
const mc = new MailCapture('mc_...', {
  baseUrl: 'http://localhost:3002',  // for local dev
  requestTimeout: 15_000,           // ms, default 10_000
})

mc.ping()PingResult

Validates your API key and returns your capture address template. Also caches your username internally so mc.address(tag) works without a network call.

const { username, address_template, example } = await mc.ping()
// username: "alice"
// address_template: "alice-{tag}@mailcapture.app"
// example: "[email protected]"

mc.waitFor(tag, options?)Capture

Long-polls the API and returns the first email captured for the given tag. This is efficient — the server holds the connection open and responds the moment an email arrives. No polling delay.

const email = await mc.waitFor('signup', {
  timeout: 15_000,     // total wait time in ms (default: 30_000)
  pollTimeout: 10,     // per-poll server timeout in seconds (default: 10, max: 30)
  after: new Date(),   // only emails received after this (default: 60s ago)
})

Throws MailCaptureTimeoutError if no email arrives before timeout.


mc.inbox(tag)Inbox

Returns a scoped Inbox object for a specific tag. Cleaner than passing the tag to every call.

const inbox = mc.inbox('password-reset')

inbox.address           // "[email protected]" (requires ping() first)
await inbox.waitFor()   // same as mc.waitFor('password-reset')
await inbox.list()      // same as mc.list({ tag: 'password-reset' })
await inbox.clear()     // same as mc.delete('password-reset')

mc.address(tag)string

Generates the capture email address for a tag. Synchronous — requires ping() to have been called first.

await mc.ping()
mc.address('signup')  // "[email protected]"

mc.list(options?)CaptureList

List recent captures (newest first).

const { items, count } = await mc.list({
  tag: 'signup',        // filter by tag
  limit: 25,            // default 25, max 100
  after: someDate,      // only captures after this date
})

mc.get(id)Capture

Get a single capture by ID.

const email = await mc.get('e0f5922d-d8a9-4b03-bc60-9507b2e2f665')

Throws MailCaptureNotFoundError if not found.


mc.delete(tag)void

Delete all captures for a tag. Use this before each test for a clean starting state.

await mc.delete('signup')

// or via inbox:
await inbox.clear()

The Capture object

interface Capture {
  id: string           // UUID
  tag: string          // e.g. "signup"
  subject: string      // email subject line
  otp: string | null   // extracted OTP/code, if detected
  body_text: string | null  // plain text body
  body_html: string | null  // HTML body
  latency_ms: number   // time from send to capture, in ms
  status: string       // e.g. "captured"
  received_at: string  // ISO 8601 timestamp
}

The otp field is automatically extracted from the email body. If your OTP is embedded in a sentence, the service finds it for you. If no code is detected, it's null.


Error handling

All errors extend MailCaptureError and have a .code property.

import {
  MailCaptureAuthError,
  MailCaptureTimeoutError,
  MailCaptureNotFoundError,
  MailCaptureNetworkError,
  MailCaptureApiError,
} from 'mailcapture'

try {
  const email = await mc.waitFor('signup', { timeout: 10_000 })
} catch (err) {
  if (err instanceof MailCaptureTimeoutError) {
    // No email arrived in time
    console.error(`Waited ${err.waitedMs}ms for tag "${err.tag}"`)
    console.error('Did the email send? Check your email service logs.')
  } else if (err instanceof MailCaptureAuthError) {
    // Invalid or revoked API key
    console.error('Check your MAILCAPTURE_API_KEY')
  } else if (err instanceof MailCaptureNetworkError) {
    // Could not reach the API
    console.error('Network error:', err.cause)
  } else {
    throw err
  }
}

| Error class | .code | When | |---|---|---| | MailCaptureAuthError | UNAUTHORIZED | Invalid or revoked API key | | MailCaptureTimeoutError | TIMEOUT | waitFor exceeded its timeout | | MailCaptureNotFoundError | NOT_FOUND | get(id) — capture doesn't exist | | MailCaptureNetworkError | NETWORK_ERROR | Could not reach the API | | MailCaptureApiError | varies | Unexpected API error |


Running tests against a local server

const mc = new MailCapture('mc_your_key_here', {
  baseUrl: 'http://localhost:3002',
})

Parallel tests

Each tag is its own inbox — safe to run in parallel.

const [signupEmail, resetEmail] = await Promise.all([
  mc.waitFor('signup', { timeout: 15_000 }),
  mc.waitFor('password-reset', { timeout: 15_000 }),
])

Just make sure your test tags are unique per test run if tests share an API key.


Environment variable

The SDK reads no environment variables automatically. Pass your key explicitly:

const mc = new MailCapture(process.env.MAILCAPTURE_API_KEY!)

Get your API key at mailcapture.app/admin/api-keys.