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

typedmailer

v2.1.1

Published

A type-safe Node.js email library with one API for Resend, Brevo, Postmark, SendGrid, Mailgun, Amazon SES, or SMTP.

Readme

TypedMailer is a type-safe email library for Node.js and TypeScript applications. It gives server-side code one API for sending transactional email with Resend, Brevo, Postmark, SendGrid, Mailgun, Amazon SES, or SMTP. Your application keeps ownership of templates, queues, retries, and business rules.

Use TypedMailer when you want to switch email providers without coupling application code to a provider SDK. Provider SDKs are optional peer dependencies, and only the selected adapter is loaded.

The separate typedmailer/webhooks entry point verifies and normalizes inbound provider events. It does not provide HTTP routing, persistence, or event processing.

Install

Install the typedmailer npm package with the provider SDK you plan to use:

npm install typedmailer resend
# or
npm install typedmailer @getbrevo/brevo
# or
npm install typedmailer nodemailer
# or
npm install typedmailer postmark
# or
npm install typedmailer @sendgrid/mail
# or
npm install typedmailer mailgun.js form-data
# or
npm install typedmailer @aws-sdk/client-sesv2

Upgrading from v1? Read the v2 migration guide, including the SES SDK minimum and attachment encoding changes.

Requires Node.js 22 or newer. Provider SDKs are optional peers and are loaded only when their adapter is selected.

For SMTP, use secure: true with implicit TLS (commonly port 465), or use STARTTLS with secure: false and requireTLS: true. Port 587 requires STARTTLS by default; other ports use opportunistic TLS unless requireTLS is set.

Supported runtime and release guarantees are documented in SUPPORT.md. Provider behavior guarantees and their test coverage are described in docs/provider-contracts.md. The scheduled and manual provider smoke workflow is documented in docs/integration-testing.md.

TypedMailer is for trusted server-side Node.js runtimes. It is not intended for browser or mobile client bundles.

Quick start

Want to try TypedMailer without provider credentials? Follow the local Mailpit quick start.

import { createMailer } from 'typedmailer';

const mailer = createMailer({
  provider: 'resend',
  apiKey: process.env.RESEND_API_KEY!,
  from: 'Example App <[email protected]>',
});

try {
  const result = await mailer.send({
    to: '[email protected]',
    subject: 'Welcome',
    text: 'Your account is ready.',
    html: '<p>Your account is ready.</p>',
  });

  console.log(result.messageId);
} finally {
  await mailer.close();
}

Providers

TypedMailer supports these email providers through the same createMailer and send API:

  • Resend for API-based email delivery.
  • Brevo for API-based email delivery.
  • Postmark for API-based email delivery.
  • SendGrid for API-based email delivery.
  • Mailgun for API-based email delivery in US or EU regions.
  • Amazon SES for API-based email delivery using AWS credentials.
  • SMTP for compatible SMTP services and local development servers such as Mailpit.

Brevo

const mailer = createMailer({
  provider: 'brevo',
  apiKey: process.env.BREVO_API_KEY!,
  from: { email: '[email protected]', name: 'Example App' },
});

Postmark

Use your Postmark server token as apiKey:

const mailer = createMailer({
  provider: 'postmark',
  apiKey: process.env.POSTMARK_SERVER_TOKEN!,
  from: 'Example App <[email protected]>',
});

SendGrid

const mailer = createMailer({
  provider: 'sendgrid',
  apiKey: process.env.SENDGRID_API_KEY!,
  from: 'Example App <[email protected]>',
});

Mailgun

Mailgun requires a sending domain and API key. Set region to 'eu' for an EU account; it defaults to 'us'.

const mailer = createMailer({
  provider: 'mailgun',
  apiKey: process.env.MAILGUN_API_KEY!,
  domain: process.env.MAILGUN_DOMAIN!,
  region: 'eu',
  from: 'Example App <[email protected]>',
});

Amazon SES

SES requires @aws-sdk/client-sesv2 >=3.797.0 <4, a region, and uses the AWS SDK credential provider chain, such as environment credentials, a shared profile, or an IAM role.

const mailer = createMailer({
  provider: 'ses',
  region: process.env.AWS_REGION!,
  from: 'Example App <[email protected]>',
});

SMTP

const mailer = createMailer({
  provider: 'smtp',
  host: process.env.SMTP_HOST ?? '127.0.0.1',
  port: Number(process.env.SMTP_PORT ?? 1025),
  secure: process.env.SMTP_SECURE === 'true',
  ...(process.env.SMTP_USER ? { user: process.env.SMTP_USER } : {}),
  ...(process.env.SMTP_PASSWORD ? { password: process.env.SMTP_PASSWORD } : {}),
  from: 'Local App <[email protected]>',
});

For local development, start Mailpit with docker run --rm -p 1025:1025 -p 8025:8025 axllent/mailpit. Messages appear at http://127.0.0.1:8025.

Message options

to accepts an email string, a { email, name } object, or an array. Provide text or html (or both). Optional fields include from, replyTo, cc, bcc, headers, attachments, metadata, and idempotencyKey; messageId is SMTP-only. Attachments may include contentId for inline images with Resend, Postmark, SendGrid, Mailgun, Amazon SES, and SMTP. Attachment content accepts UTF-8 text strings or raw bytes as Uint8Array; the provider adapters buffer it for SDK requests, so use an application-managed upload or streaming workflow for large files. Set maxAttachmentBytes on createMailer() to reject a message before loading or calling the provider when the combined UTF-8 and binary attachment content exceeds your application's memory budget. It is unset by default for backward compatibility.

Subjects and attachment filenames cannot be blank. Supplied content types and inline content IDs must be non-empty. The library does not impose a fixed attachment-size limit.

Built-in and custom mailer configuration reject unknown option keys. Named sender strings validate the enclosed email address; display names cannot contain line breaks. Message/configuration validation failures are Zod errors. Built-in provider names are preserved in result types: a Resend mailer returns SendMailResult<'resend'>.

Provider capabilities differ. Unsupported fields return a MailError with code unsupported instead of being silently ignored.

| Provider | Custom messageId | idempotencyKey | Metadata | Inline attachments | verifyConnection() | | ---------- | ------------------ | ---------------- | -------- | ------------------ | -------------------- | | Resend | No | Yes | Yes | Yes | Unsupported | | Brevo | No | No | Yes | No | Unsupported | | Postmark | No | No | Yes | Yes | Unsupported | | SendGrid | No | No | Yes | Yes | Unsupported | | Mailgun | No | No | Yes | Yes | Unsupported | | Amazon SES | No | No | Yes | Yes | Unsupported | | SMTP | Yes | No | No | Yes | Yes |

Custom providers

Use ProviderAdapter when your provider is not built in or you need to wrap an existing mail SDK. The adapter receives normalized addresses and the configured default sender; its send method must resolve with the provider message ID. Implement verifyConnection only when the provider offers a safe connection check, and use close to release resources such as pooled clients.

import { createMailer, type ProviderAdapter } from 'typedmailer';

const adapter: ProviderAdapter<'acme'> = {
  name: 'acme',
  async send(message) {
    const response = await acmeClient.sendEmail(message);
    return { messageId: response.id };
  },
  async close() {
    await acmeClient.close();
  },
};

const mailer = createMailer({ provider: adapter, from: '[email protected]' });
const result = await mailer.send({ to: '[email protected]', subject: 'Hello', text: 'Hi' });
// result.provider is typed as "acme".
await mailer.close();

The custom adapter owns provider SDK setup, field support, and error handling. Throwing an error still passes through TypedMailer error normalization. Connection verification is reported as unsupported when the adapter does not implement it. Blank or invalid custom message IDs reject with code provider and deliveryUnknown: true.

await mailer.send({
  to: [{ email: '[email protected]', name: 'Sam' }],
  subject: 'Your receipt',
  text: 'Receipt attached.',
  attachments: [{ filename: 'receipt.txt', content: 'Receipt 123' }],
  idempotencyKey: 'receipt/order-123',
});

TypedMailer does not retry sends automatically: after a network timeout the provider may already have accepted the message. Apply retries only when you understand the provider's idempotency guarantees.

Test your application flow

Use the in-memory adapter to exercise mail flows without contacting a provider:

import { createTestMailer } from 'typedmailer/testing';

const mailer = createTestMailer({ from: 'Test <[email protected]>' });
await mailer.send({ to: '[email protected]', subject: 'Hello', text: 'Hi' });
console.log(mailer.sent[0]);

Captures are independent copies of addresses, headers, metadata, and attachment bytes. clear() removes captures without reusing message IDs. Messages captured by createTestMailer return provider: 'test' so test results are not mistaken for SMTP deliveries.

Verify provider webhooks

Use verifyWebhook from typedmailer/webhooks to authenticate a raw request and receive normalized events. Your HTTP route remains responsible for preserving the raw body, responding to the provider, deduplicating events durably, and applying application changes. See Webhook verification for provider-specific configuration and security requirements, and the tested Next.js and Express route examples for raw-body capture and durable event acceptance.

Runnable examples

The repository includes complete Resend, Amazon SES, and local SMTP/Mailpit examples in examples/. From a clone, copy examples/.env.example to .env, replace the example sender and recipient with addresses valid for your account, then install the SDK for the chosen provider:

npm install typedmailer resend
node --env-file=.env examples/resend.mjs

For Amazon SES, run npm install typedmailer @aws-sdk/client-sesv2 && node --env-file=.env examples/ses.mjs; credentials come from the standard AWS SDK credential provider chain. For local SMTP, start Mailpit with docker run --rm -p 1025:1025 -p 8025:8025 axllent/mailpit, then run npm install typedmailer nodemailer && node --env-file=.env examples/smtp-mailpit.mjs. View captured messages at http://127.0.0.1:8025.

Errors and delivery

Provider and transport failures are normalized as MailError, with code, provider, retryable, deliveryUnknown, optional response status and retryAfterSeconds, and the original error in cause. Codes mean:

| Code | Meaning | | ---------------- | --------------------------------------------------------------------- | | configuration | Invalid setup, missing optional SDK, or use after close. | | authentication | The provider rejected credentials or access. | | rate_limit | The provider throttled the request. | | network | A recognized connection or timeout failure occurred. | | provider | The provider returned another failure or an invalid response. | | unsupported | The selected adapter cannot represent a requested field or operation. |

retryable is guidance from the normalized failure: recognized network failures and rate limits are retryable; API provider 5xx failures are retryable; authentication, configuration, unsupported, and SMTP 5xx failures are not. deliveryUnknown is separate: it is true when a send timeout/socket interruption, a provider 5xx, or an accepted response without a message ID means the provider may have accepted the message without returning a clear result. It stays false for verification failures, DNS lookup failures, authentication errors, rate limits, and SMTP response errors. This does not guarantee that retrying is safe. Use provider-supported idempotency where available and apply retry policy in your application. cause retains the original SDK error for diagnostics and can contain provider details; avoid logging it without reviewing your data handling policy.

SMTP results also expose optional accepted and rejected envelope recipients. Partial rejection resolves with both lists; do not resend the whole recipient list. Use the reliability guide for isolated onSend metrics hooks and tested durable inbox/outbox examples.

A successful send() means the provider accepted the request; it does not confirm inbox delivery. Verify delivery, bounce, and complaint callbacks with typedmailer/webhooks.

verifyConnection() currently supports SMTP. API provider adapters report unsupported because they do not expose a side-effect-free credential check through this API; verify credentials with a controlled provider test message.

close() is idempotent. It rejects new sends and verification calls, waits for operations already in progress, and closes the provider transport at most once. Reuse requires creating a new mailer.

Security

  • Use TypedMailer only in trusted server-side Node.js code. Never expose provider keys in browser or mobile bundles.
  • Keep secrets in environment variables or secret managers, not source control.
  • TypedMailer does not log message content or credentials.
  • See SECURITY.md to report a vulnerability.

Contributing

Issues and pull requests are welcome. Read CONTRIBUTING.md for development and contribution guidelines.

Contributors are expected to follow the Code of Conduct.

After cloning, run npm ci to install dependencies and enable the local Git hooks. Commits run staged-file lint and format checks plus unit tests. Pushes run the full npm run check quality gate, including an isolated npm tarball consumer smoke test; GitHub Actions runs it on Node.js 22 and 24 and audits dependencies before merge and publish.

License

MIT © 2026 Erol Senol