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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@jvp/dual-mailer

v0.6.2

Published

Dual mailer package

Readme

Dual Mailer

A flexible email utility that supports both Mailgun and self-hosted SMTP servers with automatic transport management, rate limiting, and retry capabilities. Perfect for applications that need to send emails through different providers.

Features

  • 📧 Supports both Mailgun and self-hosted SMTP
  • 🔄 Automatic transport pooling and management
  • 🚀 Connection reuse and optimization
  • ⏱️ Automatic cleanup of idle connections
  • 🛡️ Rate limiting for authenticated SMTP connections
  • 🧪 Development mode support
  • ♻️ Automatic retries on failed email sends

Installation

npm install @jvp/dual-mailer

Usage

Basic SMTP Setup (e.g., for Mailhog)

import { DualMailer } from '@jvp/dual-mailer';

const mailer = new DualMailer({
  host: 'localhost',
  port: 1025,
  is_dev: true
});

Authenticated SMTP Setup

const mailer = new DualMailer({
  host: 'smtp.your-domain.com',
  port: 587,
  user: 'your-username',
  password: 'your-password',
  is_dev: process.env.NODE_ENV === 'development'
});

Mailgun Setup

const mailer = new DualMailer({
  mailgun_api_key: 'your-mailgun-key',
  mailgun_domain: 'your-domain.com',
  is_dev: process.env.NODE_ENV === 'development'
});

Sending Emails

await mailer.send_mail({
  to: '[email protected]',
  subject: 'Hello World',
  html: {
    title: 'My Email',
    body: '<h1>Hello World</h1>'
  }
});

Enabling Retries

To enable retries for email sends, you can pass a retry option when creating the DualMailer instance:

const mailer = new DualMailer(config, {
  retry: {
    max_retries: 3,
    retry_delay: 1000,
    retryable_errors: ['Temporary Error']
  }
});

The retry option accepts the following properties:

| Option | Type | Default | Description | |--------|------|---------|-------------| | max_retries | number | 3 | The maximum number of retry attempts | | retry_delay | number | 1000 | The initial delay between retries in milliseconds (the delay doubles for each subsequent retry) | | retryable_errors | string[] | [] | A list of error messages that should trigger a retry (if empty, all errors will be retried) |

By default, retries are disabled. To enable them, you need to provide the retry option when creating the DualMailer instance.

Advanced HTML Emails

await mailer.send_mail({
  to: '[email protected]',
  subject: 'Styled Email',
  from: '[email protected]', // Optional
  reply_to: '[email protected]', // Optional
  html: {
    title: 'Styled Email',
    style: `
      .header { color: blue; }
      .content { padding: 20px; }
    `,
    body: `
      <div class="header">
        <h1>Welcome!</h1>
      </div>
      <div class="content">
        <p>This is a styled email.</p>
      </div>
    `
  }
});

Rate Limiting

Dual Mailer supports rate limiting for authenticated SMTP connections. You can configure the rate limiting options when creating the DualMailer instance:

const mailer = new DualMailer(config, {
  rate_limit: {
    max_connections: 5,
    max_messages_per_connection: 200,
    rate_delta: 1000,
    rate_limit: 5
  }
});

The rate_limit option accepts the following properties:

| Option | Type | Default | Description | |--------|------|---------|-------------| | max_connections | number | 5 | The maximum number of concurrent SMTP connections | | max_messages_per_connection | number | 200 | The maximum number of messages per SMTP connection | | rate_delta | number | 1000 | The time window for rate limiting in milliseconds | | rate_limit | number | 5 | The maximum number of messages to send within the rate delta |

Rate limiting is only applied to authenticated SMTP connections. For Mailgun, rate limiting is handled by the Mailgun service.

Cleanup

Make sure to clean up when shutting down your application:

await mailer.destroy();

Configuration Options

| Option | Type | Required | Description | |--------|------|----------|-------------| | host | string | No* | SMTP host | | port | number | No* | SMTP port | | user | string | No | SMTP username for authentication | | password | string | No** | SMTP password for authentication | | mailgun_api_key | string | No*** | Mailgun API key | | mailgun_domain | string | No*** | Mailgun domain | | noreply_email | string | No | Default from address | | is_dev | boolean | No | Development mode flag |

* Required if using SMTP transport (must provide both host and port)
** Required if SMTP user is provided
*** Required if using Mailgun transport (must provide both api_key and domain)

You must provide either:

  • SMTP configuration (host + port), or
  • Mailgun configuration (api_key + domain)

Email Options

| Option | Type | Required | Description | |--------|------|----------|-------------| | to | string | Yes | Recipient email address | | subject | string | Yes | Email subject | | text | string | No | Plain text version | | from | string | No | Sender address | | html | object | Yes | HTML email content | | reply_to | string | No | Reply-to address |

HTML Object Options

| Option | Type | Required | Description | |--------|------|----------|-------------| | title | string | Yes | Email title | | style | string | No | CSS styles | | body | string | Yes | HTML content |

Logging Options

The mailer accepts an optional logger in its constructor options. You can provide your own logging implementation or disable logging entirely.

Using Custom Logger

// With Winston
import winston from 'winston';
const logger = winston.createLogger({
  transports: [new winston.transports.Console()]
});

const mailer = new DualMailer(config, {
  logger: (level, message, meta) => logger.log(level, message, meta)
});

// With Pino
import pino from 'pino';
const logger = pino();

const mailer = new DualMailer(config, {
  logger: (level, message, meta) => logger[level]({ msg: message, ...meta })
});

// With custom logging service
const mailer = new DualMailer(config, {
  logger: (level, message, meta) => {
    MyLoggingService.log({
      severity: level,
      message,
      timestamp: new Date(),
      ...meta
    });
  }
});

Disabling Logging

const mailer = new DualMailer(config, { silent: true });

The logger function receives:

  • level: 'info' | 'warn' | 'error'
  • message: String description of the event
  • meta: Object with additional context (timestamps, email details, errors)

Development Mode

When is_dev is true:

  • SSL is disabled for SMTP
  • Failed emails log their content instead of throwing
  • Transport cleanup is disabled

Error Handling

try {
  await mailer.send_mail({
    to: '[email protected]',
    subject: 'Test Email',
    html: {
      title: 'Test',
      body: '<h1>Hello</h1>'
    }
  });
} catch (error) {
  console.error('Failed to send email:', error);
}

Best Practices

  1. Always call destroy() when shutting down your application
  2. Use environment variables for sensitive configuration
  3. Implement proper error handling
  4. Set appropriate timeouts for your use case
  5. For local development, use tools like Mailhog with simple SMTP configuration
  6. Use authenticated SMTP or Mailgun for production environments
  7. Configure appropriate rate limiting settings for your use case

License

MIT

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.