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

@bernierllc/email-service

v4.0.0

Published

Comprehensive email service orchestrating template management, multi-provider delivery, tracking, and subscriber management

Downloads

535

Readme

@bernierllc/email-service

Comprehensive email service orchestrating template management, multi-provider delivery, tracking, and subscriber management.

Features

  • Multi-Provider Support - SendGrid, Mailgun, AWS SES, and SMTP
  • Template Management - Create, update, and render email templates with variable substitution
  • Magic Link Authentication - Secure, time-limited authentication links for passwordless login
  • Automatic Retry Logic - Exponential backoff with jitter for transient failures
  • Email Validation - Validate recipient addresses before sending (syntax, domain, MX records)
  • Delivery Tracking - Track email delivery, opens, clicks, and bounces
  • Subscriber Management - Manage subscribers, lists, and preferences
  • Queue Integration - Schedule and send emails in background
  • Webhook Handling - Process provider webhooks for delivery events
  • Database Persistence - Store templates, delivery records, and subscribers
  • Analytics - Comprehensive delivery statistics and reporting

Installation

npm install @bernierllc/email-service

Peer Dependencies

Install email provider(s) you need:

# SendGrid
npm install @sendgrid/mail

# SMTP (included)
npm install nodemailer

Usage

Quick Start

import { EmailService } from '@bernierllc/email-service';

const service = new EmailService({
  providers: [
    {
      type: 'smtp',
      smtp: {
        host: 'smtp.gmail.com',
        port: 587,
        secure: false,
        auth: { user: '[email protected]', pass: 'password' }
      }
    }
  ],
  defaultProvider: 'smtp',
  database: {
    type: 'sqlite',
    database: './data/email.db'
  },
  templates: {
    cacheEnabled: true,
    cacheTTL: 3600
  },
  tracking: {
    enabled: true,
    domain: 'yourdomain.com'
  }
});

await service.initialize();

// Send an email
const result = await service.send({
  from: '[email protected]',
  to: ['[email protected]'],
  subject: 'Welcome!',
  html: '<h1>Hello!</h1><p>Welcome to our service.</p>'
});

console.log(`Email sent: ${result.messageId}`);

Core Concepts

1. Multi-Provider Email Sending

// Configure multiple providers
const service = new EmailService({
  providers: [
    {
      type: 'sendgrid',
      apiKey: process.env.SENDGRID_API_KEY
    },
    {
      type: 'mailgun',
      apiKey: process.env.MAILGUN_API_KEY,
      domain: 'mg.yourdomain.com'
    },
    {
      type: 'ses',
      region: 'us-east-1',
      apiKey: process.env.AWS_ACCESS_KEY,
      secretKey: process.env.AWS_SECRET_KEY
    },
    {
      type: 'smtp',
      smtp: {
        host: 'smtp.gmail.com',
        port: 587,
        secure: false,
        auth: {
          user: process.env.SMTP_USER,
          pass: process.env.SMTP_PASS
        }
      }
    }
  ],
  defaultProvider: 'sendgrid',
  database: { type: 'sqlite', database: './email.db' }
});

// Send via specific provider
await service.send({
  from: '[email protected]',
  to: ['[email protected]'],
  subject: 'Test',
  html: '<p>Test</p>',
  provider: 'mailgun' // Override default
});

2. Template Management

// Create template
const template = await service.createTemplate({
  name: 'welcome-email',
  subject: 'Welcome {{name}}!',
  html: `
    <h1>Hello {{name}}</h1>
    <p>Welcome to {{company}}!</p>
    <p>Your account email is: {{email}}</p>
  `,
  text: 'Hello {{name}}, welcome to {{company}}!'
});

// Send using template
await service.send({
  from: '[email protected]',
  to: ['[email protected]'],
  templateId: template.id,
  templateData: {
    name: 'John Doe',
    company: 'Acme Corp',
    email: '[email protected]'
  }
});

// Update template
await service.updateTemplate(template.id, {
  subject: 'Welcome to {{company}}, {{name}}!',
  html: '<h1>Welcome {{name}}!</h1>'
});

// Get template
const retrieved = await service.getTemplate(template.id);
console.log(retrieved.variables); // ['name', 'company', 'email']

// Delete template
await service.deleteTemplate(template.id);

3. Magic Link Authentication

Send secure, time-limited authentication links for passwordless login, email verification, password resets, and more.

// Configure magic link support
const service = new EmailService({
  providers: [/* ... */],
  database: {/* ... */},
  magicLink: {
    secret: process.env.MAGIC_LINK_SECRET, // Required: Secret key for signing tokens
    expirationMinutes: 30,                  // Optional: Default 60 minutes
    issuer: 'YourApp'                       // Optional: Token issuer name
  }
});

await service.initialize();

// Send magic link for passwordless login
const result = await service.sendMagicLink({
  recipient: '[email protected]',
  purpose: 'sign in',
  redirectUrl: 'https://app.example.com/auth/verify',
  subject: 'Your Login Link',              // Optional: Custom subject
  expirationMinutes: 15                    // Optional: Override default expiration
});

console.log(result.magicLink);  // https://app.example.com/auth/verify?token=eyJ...
console.log(result.token);      // eyJ... (signed JWT token)
console.log(result.expiresAt);  // 2025-01-15T12:45:00.000Z

// Send with custom data in token
const resetResult = await service.sendMagicLink({
  recipient: '[email protected]',
  purpose: 'reset your password',
  redirectUrl: 'https://app.example.com/reset-password',
  customData: {
    userId: '12345',
    action: 'password-reset'
  }
});

// Use custom template for magic link email
const welcomeResult = await service.sendMagicLink({
  recipient: '[email protected]',
  purpose: 'verify your email',
  redirectUrl: 'https://app.example.com/verify',
  template: 'welcome-verification',  // Use existing template
  templateData: {
    userName: 'John Doe',
    companyName: 'Acme Corp'
  }
});

Magic Link Features:

  • ✅ Secure signed tokens using @bernierllc/crypto-utils
  • ✅ Configurable expiration times
  • ✅ Custom data embedded in token
  • ✅ Default professional email template (or use your own)
  • ✅ Automatic URL parameter handling (existing query params preserved)
  • ✅ Support for all email providers

Verifying Magic Link Tokens:

import { verifySignedToken } from '@bernierllc/crypto-utils';

// In your auth/verify endpoint
app.get('/auth/verify', async (req, res) => {
  const token = req.query.token as string;

  try {
    const payload = await verifySignedToken(
      token,
      process.env.MAGIC_LINK_SECRET!
    );

    // Token is valid - log user in
    console.log(payload.email);      // [email protected]
    console.log(payload.purpose);    // 'sign in'
    console.log(payload.customData); // Any custom data you included

    // Create session and redirect
    req.session.userId = payload.email;
    res.redirect('/dashboard');
  } catch (error) {
    // Token expired, invalid, or tampered with
    res.status(401).send('Invalid or expired link');
  }
});

4. Retry Logic & Email Validation

Automatic retry with exponential backoff and email address validation.

const service = new EmailService({
  providers: [/* ... */],
  database: {/* ... */},

  // Retry configuration (optional)
  retry: {
    maxRetries: 3,                  // Number of retry attempts
    initialDelayMs: 1000,           // Initial delay between retries
    maxDelayMs: 30000,              // Maximum delay
    jitter: true,                   // Add randomness to delays
    enableMetrics: true             // Track retry metrics
  },

  // Email validation (optional)
  validation: {
    validateRecipients: true,       // Validate email addresses before sending
    strict: true                    // Use strict validation rules
  }
});

// Emails will automatically retry on transient failures
const result = await service.send({
  to: '[email protected]',
  subject: 'Test',
  html: '<p>This will retry up to 3 times if it fails</p>'
});

console.log(result.retryCount); // Number of retries that occurred

Retry Features:

  • ✅ Exponential backoff with jitter to avoid thundering herd
  • ✅ Configurable max retries and delays
  • ✅ Automatic retry on transient failures
  • ✅ Retry metrics tracking (attempts, successes, failures)
  • ✅ Integrates with @bernierllc/retry-policy, retry-state, and retry-metrics

Validation Features:

  • ✅ Syntax validation (RFC 5322 compliance)
  • ✅ Domain validation (MX record checks when strict mode enabled)
  • ✅ Disposable email detection
  • ✅ Multiple recipient validation
  • ✅ Integrates with @bernierllc/email-validator

5. Delivery Tracking

// Send email
const result = await service.send({
  from: '[email protected]',
  to: ['[email protected]'],
  subject: 'Order Confirmation',
  html: '<h1>Thanks for your order!</h1>'
});

// Get delivery record
const delivery = await service.getDelivery(result.deliveryId);
console.log(delivery.status); // 'sent', 'delivered', 'opened', 'clicked', etc.

// Track opens
await service.trackOpen(result.deliveryId);

// Track clicks
await service.trackClick(result.deliveryId);

// Get statistics
const stats = await service.getDeliveryStats();
console.log(`Open rate: ${stats.openRate}%`);
console.log(`Click rate: ${stats.clickRate}%`);
console.log(`Bounce rate: ${stats.bounceRate}%`);

// Get statistics for date range
const monthStats = await service.getDeliveryStats(
  new Date('2025-01-01'),
  new Date('2025-01-31')
);

4. Subscriber Management

// Create subscriber
const subscriber = await service.createSubscriber({
  email: '[email protected]',
  name: 'John Doe',
  lists: ['newsletter', 'updates'],
  tags: ['premium', 'early-adopter'],
  metadata: {
    source: 'website',
    signupDate: new Date()
  }
});

// Get subscriber
const found = await service.getSubscriber(subscriber.id);

// Update subscriber
await service.updateSubscriber(subscriber.id, {
  name: 'John Smith',
  tags: ['premium', 'verified']
});

// Unsubscribe
await service.unsubscribe('[email protected]');

// Create subscriber list
const list = await service.createList(
  'Monthly Newsletter',
  'Our monthly product updates and news'
);

5. Scheduled Emails

// Configure queue
const service = new EmailService({
  providers: [/* ... */],
  database: { type: 'sqlite', database: './email.db' },
  queue: {
    backend: new MemoryBackend(),
    concurrency: 5,
    retryPolicy: {
      maxAttempts: 3,
      backoff: { type: 'exponential', delay: 1000 }
    }
  }
});

// Schedule for later
await service.send({
  from: '[email protected]',
  to: ['[email protected]'],
  subject: 'Reminder',
  html: '<p>This is your reminder</p>',
  scheduledAt: new Date(Date.now() + 24 * 60 * 60 * 1000) // 24 hours
});

// Send with priority
await service.send({
  from: '[email protected]',
  to: ['[email protected]'],
  subject: 'URGENT: System Alert',
  html: '<p>Critical alert</p>',
  priority: 'high' // high, normal, low
});

6. Bulk Sending

const results = await service.sendBulk([
  {
    from: '[email protected]',
    to: ['[email protected]'],
    subject: 'Personalized for User 1',
    html: '<p>Hello User 1</p>'
  },
  {
    from: '[email protected]',
    to: ['[email protected]'],
    subject: 'Personalized for User 2',
    html: '<p>Hello User 2</p>'
  }
]);

results.forEach((result, index) => {
  console.log(`Email ${index + 1}: ${result.success ? 'sent' : 'failed'}`);
});

7. Webhook Handling

// Register webhook handler
service.registerWebhookHandler('delivered', async (event) => {
  console.log(`Email ${event.messageId} was delivered`);
  // Update your database, trigger workflows, etc.
});

service.registerWebhookHandler('opened', async (event) => {
  console.log(`Email ${event.messageId} was opened`);
  // Track user engagement
});

service.registerWebhookHandler('clicked', async (event) => {
  console.log(`Link clicked in ${event.messageId}`);
  // Track click-through rates
});

service.registerWebhookHandler('bounced', async (event) => {
  console.log(`Email ${event.messageId} bounced`);
  // Update subscriber status
  await service.unsubscribe(event.data.email);
});

// Process webhook from provider
await service.handleWebhook({
  provider: 'sendgrid',
  event: 'delivered',
  messageId: 'msg_123',
  timestamp: new Date(),
  data: { /* provider-specific data */ }
});

API Reference

EmailService

Constructor

new EmailService(config: EmailServiceConfig)

Configuration:

interface EmailServiceConfig {
  providers: ProviderConfig[];
  defaultProvider?: EmailProvider;
  database: DatabaseConfig;
  queue?: QueueOptions;
  templates?: {
    cacheEnabled?: boolean;
    cacheTTL?: number;
  };
  tracking?: {
    enabled?: boolean;
    domain?: string;
  };
  compliance?: {
    unsubscribeLink?: boolean;
    footerRequired?: boolean;
  };
}

interface ProviderConfig {
  type: 'sendgrid' | 'mailgun' | 'ses' | 'smtp';
  apiKey?: string;
  domain?: string;
  region?: string;
  smtp?: SMTPConfig;
  rateLimit?: RateLimitConfig;
}

Methods

Email Sending
  • send(request: EmailRequest): Promise<SendEmailResult> - Send email
  • sendBulk(requests: EmailRequest[]): Promise<SendEmailResult[]> - Send multiple emails
Template Management
  • createTemplate(request: TemplateCreateRequest): Promise<EmailTemplate> - Create template
  • getTemplate(id: string): Promise<EmailTemplate | null> - Get template
  • updateTemplate(id: string, update: TemplateUpdateRequest): Promise<EmailTemplate> - Update template
  • deleteTemplate(id: string): Promise<boolean> - Delete template
Delivery Tracking
  • getDelivery(id: string): Promise<DeliveryRecord | null> - Get delivery record
  • trackOpen(deliveryId: string): Promise<void> - Track email open
  • trackClick(deliveryId: string): Promise<void> - Track link click
  • getDeliveryStats(startDate?: Date, endDate?: Date): Promise<DeliveryStats> - Get statistics
Subscriber Management
  • createSubscriber(request: SubscriberCreateRequest): Promise<Subscriber> - Create subscriber
  • getSubscriber(id: string): Promise<Subscriber | null> - Get subscriber
  • updateSubscriber(id: string, update: SubscriberUpdateRequest): Promise<Subscriber> - Update subscriber
  • unsubscribe(email: string): Promise<boolean> - Unsubscribe email
  • createList(name: string, description?: string): Promise<SubscriberList> - Create list
Webhook Handling
  • registerWebhookHandler(event: string, handler: WebhookHandler): void - Register handler
  • handleWebhook(event: WebhookEvent): Promise<void> - Process webhook
Lifecycle
  • initialize(): Promise<void> - Initialize service
  • shutdown(): Promise<void> - Cleanup and shutdown

Type Definitions

interface EmailRequest {
  from: EmailAddress | string;
  to: EmailAddress[] | string[];
  cc?: EmailAddress[] | string[];
  bcc?: EmailAddress[] | string[];
  subject: string;
  html?: string;
  text?: string;
  templateId?: string;
  templateData?: TemplateContext;
  attachments?: EmailAttachment[];
  headers?: Record<string, string>;
  tags?: string[];
  metadata?: Record<string, any>;
  priority?: 'high' | 'normal' | 'low';
  scheduledAt?: Date;
  provider?: EmailProvider;
}

interface SendEmailResult {
  success: boolean;
  messageId?: string;
  deliveryId?: string;
  provider?: EmailProvider;
  error?: string;
  timestamp: Date;
  queuedForLater?: boolean;
}

interface DeliveryStats {
  total: number;
  sent: number;
  delivered: number;
  opened: number;
  clicked: number;
  bounced: number;
  failed: number;
  openRate: number;
  clickRate: number;
  bounceRate: number;
}

enum DeliveryStatus {
  QUEUED = 'queued',
  SENDING = 'sending',
  SENT = 'sent',
  DELIVERED = 'delivered',
  OPENED = 'opened',
  CLICKED = 'clicked',
  BOUNCED = 'bounced',
  FAILED = 'failed',
  UNSUBSCRIBED = 'unsubscribed'
}

Best Practices

Error Handling

try {
  await service.send(emailRequest);
} catch (error) {
  if (error instanceof ProviderError) {
    // Switch to backup provider
    console.error('Provider failed:', error.provider);
  } else if (error instanceof TemplateError) {
    // Template rendering failed
    console.error('Template error:', error.message);
  } else if (error instanceof DeliveryError) {
    // Delivery failed
    console.error('Delivery error:', error.message);
  }
}

Template Variables

// Always validate template data matches template variables
const template = await service.getTemplate(templateId);
const missingVars = template.variables.filter(
  v => !(v in templateData)
);
if (missingVars.length > 0) {
  throw new Error(`Missing template variables: ${missingVars.join(', ')}`);
}

Rate Limiting

// Configure per-provider rate limits
const service = new EmailService({
  providers: [
    {
      type: 'sendgrid',
      apiKey: process.env.SENDGRID_API_KEY,
      rateLimit: {
        maxPerSecond: 100,
        maxPerMinute: 5000,
        maxPerHour: 100000
      }
    }
  ],
  // ...
});

Monitoring

// Regular health monitoring
setInterval(async () => {
  const stats = await service.getDeliveryStats();

  if (stats.bounceRate > 5) {
    console.warn('High bounce rate detected:', stats.bounceRate);
  }

  if (stats.failed > 100) {
    console.error('High failure count:', stats.failed);
  }
}, 60000); // Every minute

Dependencies

This package orchestrates:

Integration Status

  • Logger: Integrated - Uses @bernierllc/logger for structured logging
  • Docs-Suite: Ready - Markdown documentation available
  • NeverHub integration: Planned - Service discovery and event bus integration with @bernierllc/neverhub-adapter

Examples

See the examples directory for complete working examples:

  • basic-smtp.ts - Basic SMTP email sending
  • templates.ts - Template management and rendering
  • tracking.ts - Delivery tracking and analytics
  • subscribers.ts - Subscriber management
  • webhooks.ts - Webhook handling
  • multi-provider.ts - Multi-provider configuration

License

Copyright (c) 2025 Bernier LLC. All rights reserved.

This package is proprietary software licensed for use only within the scope of the project it was delivered for.