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

@brijesh575/email-core

v1.0.0

Published

Production-grade, multi-tenant email service with pluggable providers

Readme

@brijesh/email

A production-grade, multi-tenant email service for Node.js with pluggable providers, retry/fallback, rate limiting, and Handlebars templating.

Features

  • Multi-provider — SMTP (Nodemailer), AWS SES, SendGrid, or any custom provider
  • Multi-tenant — per-tenant provider, credentials, templates, and rate limits
  • Layered config — global → environment → tenant → per-request overrides
  • Provider fallback — automatic failover to a backup provider
  • Retry with backoff — configurable exponential retry on failure
  • In-memory rate limiting — per-tenant abuse prevention
  • Template engine — Handlebars with compile caching
  • Hooks/middlewarebeforeSend, afterSend, onError
  • Preview mode — log emails in development instead of sending
  • Health checkemailClient.healthCheck() for monitoring
  • Pluggable logger — default console logger with secret masking
  • ESM + CJS — dual output with full TypeScript types
  • Zero lock-in — optional peer deps for SES and SendGrid

Installation

npm install @brijesh/email

Optional providers

# AWS SES
npm install @aws-sdk/client-ses

# SendGrid
npm install @sendgrid/mail

Quick Start

import { EmailClient } from "@brijesh/email";

const client = new EmailClient({
  defaultFrom: "[email protected]",
  provider: {
    type: "smtp",
    smtp: {
      host: "smtp.example.com",
      port: 587,
      auth: { user: "user", pass: "pass" },
    },
  },
});

await client.send({
  to: "[email protected]",
  subject: "Hello!",
  text: "Welcome to our app.",
});

await client.destroy();

Multi-Tenant Usage

const client = new EmailClient({
  defaultFrom: "[email protected]",
  provider: {
    type: "smtp",
    smtp: { host: "smtp.platform.com", port: 587, auth: { user: "u", pass: "p" } },
  },
  tenants: {
    acme: {
      provider: {
        type: "ses",
        ses: { region: "us-east-1", from: "[email protected]" },
      },
      from: "[email protected]",
      templateDir: "./templates/acme",
      rateLimit: { enabled: true, maxPerMinute: 100, maxPerHour: 5000 },
    },
    startup: {
      provider: {
        type: "sendgrid",
        sendGrid: { apiKey: "SG.xxx", from: "[email protected]" },
      },
      from: "[email protected]",
    },
  },
});

// Sends via SES with Acme's credentials
await client.send({
  tenantId: "acme",
  to: "[email protected]",
  subject: "Welcome",
  template: "welcome",
  data: { name: "John" },
});

// Register tenant at runtime
client.registerTenant("enterprise", {
  provider: { type: "smtp", smtp: { host: "mail.ent.com", port: 465, secure: true, auth: { user: "a", pass: "b" } } },
  from: "[email protected]",
});

Provider Setup

SMTP

{
  type: "smtp",
  smtp: {
    host: "smtp.example.com",
    port: 587,
    secure: false,
    auth: { user: "user", pass: "pass" },
    pool: true,          // connection pooling
    maxConnections: 5,
  },
}

AWS SES

Requires @aws-sdk/client-ses as a peer dependency.

{
  type: "ses",
  ses: {
    region: "us-east-1",
    accessKeyId: "AKIA...",       // optional if using IAM roles
    secretAccessKey: "secret...",
    from: "[email protected]",
  },
}

SendGrid

Requires @sendgrid/mail as a peer dependency.

{
  type: "sendgrid",
  sendGrid: {
    apiKey: "SG.xxxx",
    from: "[email protected]",
  },
}

Custom Provider

import type { EmailProvider, NormalizedEmailOptions, SendResult } from "@brijesh/email";

class MyProvider implements EmailProvider {
  readonly name = "my-provider";

  async send(options: NormalizedEmailOptions): Promise<SendResult> {
    // your logic
    return {
      messageId: "custom-id",
      provider: this.name,
      accepted: options.to,
      rejected: [],
      timestamp: new Date(),
    };
  }
}

// Use it
{
  type: "custom",
  custom: new MyProvider(),
}

Templates (Handlebars)

Place .hbs files in your template directory:

templates/
  welcome.hbs       # HTML template
  welcome.text.hbs  # Plain text (optional)
<!-- templates/welcome.hbs -->
<h1>Welcome, {{name}}!</h1>
<p>Thanks for joining {{company}}.</p>
await client.send({
  to: "[email protected]",
  subject: "Welcome",
  template: "welcome",
  data: { name: "Alice", company: "Acme" },
});

Attachments

await client.send({
  to: "[email protected]",
  subject: "Invoice",
  text: "Please find attached.",
  attachments: [
    { filename: "invoice.pdf", path: "/path/to/invoice.pdf" },
    { filename: "data.csv", content: Buffer.from("a,b,c\n1,2,3") },
  ],
});

Provider Fallback

const client = new EmailClient({
  defaultFrom: "[email protected]",
  provider: { type: "ses", ses: { region: "us-east-1", from: "[email protected]" } },
  fallbackProvider: { type: "smtp", smtp: { host: "smtp.backup.com", port: 587 } },
});

If the primary provider fails after retries, the fallback provider is tried automatically.


Retry Configuration

{
  retry: {
    enabled: true,
    maxAttempts: 3,
    initialDelayMs: 1000,
    maxDelayMs: 30000,
    backoffMultiplier: 2,
  },
}

Rate Limiting

{
  rateLimit: {
    enabled: true,
    maxPerMinute: 60,
    maxPerHour: 1000,
  },
}

Throws RateLimitError when limits are exceeded.


Hooks

const client = new EmailClient({
  // ...
  hooks: {
    beforeSend: [(options) => {
      // Modify options, add tracking headers, etc.
      return { ...options, headers: { ...options.headers, "X-Track": "abc" } };
    }],
    afterSend: [(result, options) => {
      console.log(`Sent ${result.messageId} to ${options.to}`);
    }],
    onError: [(error, options) => {
      console.error(`Failed to send to ${options.to}: ${error.message}`);
    }],
  },
});

Preview Mode

In development, log emails instead of sending:

const client = new EmailClient({
  preview: true,
  defaultFrom: "dev@localhost",
  provider: { type: "smtp", smtp: { host: "localhost", port: 1025 } },
});

Health Check

const health = await client.healthCheck();
// { status: "healthy", providers: { ... }, timestamp: Date }

Environment Variables

The library reads .env variables as fallback for SMTP when no provider is configured:

| Variable | Description | |---|---| | NODE_ENV | development, staging, production | | SMTP_HOST | SMTP server host | | SMTP_PORT | SMTP server port (default: 587) | | SMTP_SECURE | Use TLS (true/false) | | SMTP_USER | SMTP username | | SMTP_PASS | SMTP password | | SMTP_FROM | Default from address |


Error Handling

import { EmailError, ProviderError, ConfigError, ValidationError, RateLimitError } from "@brijesh/email";

try {
  await client.send({ /* ... */ });
} catch (err) {
  if (err instanceof ValidationError) {
    console.log("Invalid input:", err.field);
  } else if (err instanceof RateLimitError) {
    console.log("Rate limited:", err.tenantId);
  } else if (err instanceof ProviderError) {
    console.log("Provider failed:", err.provider, err.cause);
  }
}

All errors use the standard cause chain — never leak credentials.


API Reference

new EmailClient(config?)

Creates a new client instance.

client.send(options)

Send an email. Returns Promise<SendResult>.

client.healthCheck()

Returns Promise<HealthCheckResult>.

client.registerTenant(id, config)

Register or update a tenant at runtime.

client.removeTenant(id)

Remove a tenant and clean up its resources.

client.destroy()

Gracefully shut down all providers and caches.


Build

npm run build     # ESM + CJS + types via tsup
npm test          # Vitest
npm run lint      # TypeScript check

License

MIT