@bernierllc/email-sender

中文文档
5.14.0发布于 2026/11/05MITTypeScriptCommonJS近 30 天下载 2498

Email sending with provider-agnostic interface and SendGrid advanced features (templates, verified senders, rate limiting)

原始使用文档
文档目录38 节
  1. Features
  2. Installation
  3. Quick Start
  4. Basic Usage
  5. SendGrid Configuration
  6. AWS SES Configuration
  7. Mailgun Configuration
  8. Postmark Configuration
  9. API Reference
  10. EmailSender
  11. Types
  12. Provider Capabilities
  13. EmailProviderCapabilities Interface
  14. CapabilityEntry
  15. Per-Provider Capability Summary
  16. Declaring Capabilities in Custom Providers
  17. Zod Validation
  18. Staging Email Safety
  19. Staging with Whitelist (Block Non-Whitelisted)
  20. Staging with Rewrite (Redirect to Test Email)
  21. Production (All Emails Allowed)
  22. Check Recipient Safety Before Sending
  23. Dynamic Configuration Based on Environment
  24. Examples
  25. Send Email with Attachment
  26. Send Email with Custom Headers
  27. Batch Email with Different Content
  28. Error Handling
  29. Provider Support
  30. Currently Supported
  31. Adding New Providers
  32. Testing
  33. Contributing
  34. Integration Status
  35. Graceful Degradation
  36. License
  37. Support
  38. Per-message analytics parameters

@bernierllc/email-sender

A core utility for basic email sending with provider-agnostic interface.

Features

  • Provider-Agnostic: Support for multiple email providers (SMTP, SendGrid, SES, Mailgun, Postmark)
  • Staging Email Safety: Prevent accidental email sends in non-production environments
  • TypeScript: Full TypeScript support with strict typing
  • Validation: Comprehensive email and configuration validation
  • Attachments: Support for file attachments
  • Batch Sending: Send multiple emails efficiently
  • Error Handling: Robust error handling and reporting
  • Testing: Comprehensive test coverage

Installation

npm install @bernierllc/email-sender

Note: This package uses peer dependencies. You must install them separately:

# For SMTP provider
npm install nodemailer

# For AWS SES provider
npm install aws-sdk

Quick Start

Basic Usage

import { EmailSender } from '@bernierllc/email-sender';

// Configure SMTP provider
const config = {
  provider: 'smtp' as const,
  host: 'smtp.gmail.com',
  port: 587,
  secure: false,
  username: '[email protected]',
  password: 'your-app-password',
  fromEmail: '[email protected]',
  fromName: 'Your App',
};

// Create email sender
const emailSender = new EmailSender(config);

// Send an email
const result = await emailSender.sendEmail({
  toEmail: '[email protected]',
  toName: 'John Doe',
  subject: 'Welcome to our app!',
  htmlContent: '<h1>Welcome!</h1><p>Thank you for joining us.</p>',
  textContent: 'Welcome! Thank you for joining us.',
});

if (result.success) {
  console.log('Email sent successfully:', result.messageId);
} else {
  console.error('Failed to send email:', result.errorMessage);
}

SendGrid Configuration

import { EmailSender } from '@bernierllc/email-sender';

const config = {
  provider: 'sendgrid' as const,
  apiKey: 'your-sendgrid-api-key',
  fromEmail: '[email protected]',
  fromName: 'Your App',
  // Optional: point every SendGrid call (mail send, templates, senders) at
  // another host, e.g. a local mock in tests. Give the bare origin; a
  // trailing `/v3` is also accepted. Defaults to https://api.sendgrid.com.
  endpoint: process.env.SENDGRID_API_BASE_URL,
};

const emailSender = new EmailSender(config);

AWS SES Configuration

import { EmailSender } from '@bernierllc/email-sender';

const config = {
  provider: 'ses' as const,
  apiKey: 'your-aws-access-key',
  secretKey: 'your-aws-secret-key',
  region: 'us-east-1',
  fromEmail: '[email protected]',
  fromName: 'Your App',
  sandbox: false, // Set to true if in SES sandbox mode
};

const emailSender = new EmailSender(config);

Mailgun Configuration

import { EmailSender } from '@bernierllc/email-sender';

const config = {
  provider: 'mailgun' as const,
  apiKey: 'your-mailgun-api-key',
  domain: 'yourdomain.com',
  region: 'us', // or 'eu' for European region
  fromEmail: '[email protected]',
  fromName: 'Your App',
};

const emailSender = new EmailSender(config);

Postmark Configuration

import { EmailSender } from '@bernierllc/email-sender';

const config = {
  provider: 'postmark' as const,
  apiKey: 'your-postmark-api-key',
  accountToken: 'your-postmark-account-token', // Optional, for template operations
  fromEmail: '[email protected]',
  fromName: 'Your App',
};

const emailSender = new EmailSender(config);

API Reference

EmailSender

The main class for sending emails.

Constructor

new EmailSender(config: EmailProviderConfig)

Methods

sendEmail(email: EmailMessage): Promise<SendResult>

Send a single email.

const result = await emailSender.sendEmail({
  toEmail: '[email protected]',
  subject: 'Test Email',
  htmlContent: '<h1>Hello</h1>',
});
sendBatch(emails: EmailMessage[]): Promise<SendResult[]>

Send multiple emails in a batch.

const results = await emailSender.sendBatch([
  { toEmail: '[email protected]', subject: 'Email 1', htmlContent: '<h1>Email 1</h1>' },
  { toEmail: '[email protected]', subject: 'Email 2', htmlContent: '<h1>Email 2</h1>' },
]);
validateConfiguration(): EmailValidationResult

Validate the provider configuration.

const validation = emailSender.validateConfiguration();
if (!validation.isValid) {
  console.error('Configuration errors:', validation.errors);
}
validateEmailMessage(email: EmailMessage): EmailValidationResult

Validate an email message before sending.

const validation = emailSender.validateEmailMessage(email);
if (!validation.isValid) {
  console.error('Email validation errors:', validation.errors);
}
testConnection(): Promise<boolean>

Test the connection to the email provider.

const isConnected = await emailSender.testConnection();
if (isConnected) {
  console.log('Connection successful');
} else {
  console.error('Connection failed');
}
getCapabilities(): EmailProviderCapabilities

Get the capabilities of the current provider.

const capabilities = emailSender.getCapabilities();
console.log('Supports attachments:', capabilities.supportsAttachments);
console.log('Max batch size:', capabilities.maxBatchSize);

Types

EmailMessage

interface EmailMessage {
  toEmail: string;
  toName?: string;
  fromEmail?: string;
  fromName?: string;
  subject: string;
  htmlContent?: string;
  textContent?: string;
  replyTo?: string;
  headers?: Record<string, string>;
  attachments?: EmailAttachment[];
  metadata?: Record<string, any>;
  /** Per-message open, click and UTM tracking; omitted = the provider account's default. */
  tracking?: { opens?: boolean; clicks?: boolean; utm?: boolean };
}

tracking: { clicks: false } keeps links unrewritten in both the HTML and text parts on the providers below, which one-time links (sign-in, invites, password resets) need. It maps to SendGrid tracking_settings, Mailgun o:tracking-clicks/o:tracking-opens, Postmark TrackLinks/TrackOpens (Postmark ignores opens: false while open tracking is on at the server level) and Mandrill track_clicks/track_opens; providers without tracking ignore it. SMTP2GO sets tracking per API key only and ignores the override, so use a key with click tracking off for one-time links.

tracking: { utm: false } stops the provider appending analytics query parameters (UTM tags) to links; leave it undefined for the account default. It does not stop click tracking: links stay verbatim only with { clicks: false, utm: false }. utm: true forces the tags on using the account's UTM values. Only SendGrid honours it (tracking_settings.ganalytics); every other provider ignores it, and getCapabilities().features.utmTracking says which is which.

EmailAttachment

interface EmailAttachment {
  filename: string;
  content: string; // Base64 encoded
  contentType?: string;
  disposition?: 'attachment' | 'inline';
}

SendResult

interface SendResult {
  success: boolean;
  messageId?: string;
  errorMessage?: string;
  providerResponse?: any;
  metadata?: Record<string, any>;
}

EmailProviderConfig

interface EmailProviderConfig {
  provider: 'sendgrid' | 'ses' | 'mailgun' | 'postmark' | 'smtp';
  apiKey?: string;
  region?: string;
  endpoint?: string;
  fromEmail: string;
  fromName?: string;
  replyTo?: string;
  webhookUrl?: string;
  webhookSecret?: string;
  sandbox?: boolean;
  // SMTP specific options
  host?: string;
  port?: number;
  secure?: boolean;
  username?: string;
  password?: string;
}

Provider Capabilities

Each provider exposes its capabilities through the getCapabilities() method. The capabilities interface includes both legacy boolean flags and an expanded features map for fine-grained capability inspection.

EmailProviderCapabilities Interface

interface EmailProviderCapabilities {
  // Legacy boolean flags (always available)
  supportsBatch: boolean;
  supportsTemplates: boolean;
  supportsWebhooks: boolean;
  supportsAttachments: boolean;
  supportsCustomHeaders: boolean;
  supportsDeliveryTracking: boolean;
  maxBatchSize: number;
  maxAttachmentSize: number;

  // Expanded feature map (optional, populated by capability-aware providers)
  features?: Record<string, CapabilityEntry>;
}

CapabilityEntry

Each entry in the features map describes how a specific feature is supported:

type CapabilitySource = 'provider' | 'platform' | 'enhanced' | 'unsupported';

interface CapabilityEntry {
  source: CapabilitySource;      // How the feature is implemented
  description: string;           // Human-readable description
  limitations?: string;          // Known limitations (e.g., "72hr max scheduling window")
  degradation?: {                // Present when platform polyfill has a degradation strategy
    strategy: string;
    description: string;
    docUrl: string;
  } | null;
  overridable: boolean;          // Whether the entry can be overridden at runtime
}

Per-Provider Capability Summary

ProviderBatchTemplatesWebhooksAttachmentsCustom HeadersDelivery Tracking
SendGridYes (personalizations)Yes (dynamic templates)Yes (Event Webhook)YesYesYes
MailgunYes (recipient variables)Yes (stored templates)YesYesYesYes
PostmarkYes (batch API)Yes (server-side)YesYesYesYes
SESYes (SendBulkEmail)Yes (email templates)Yes (via SNS)Yes (raw email)YesYes (via SNS)
SMTPSequential*No server-sideNoYes (MIME)YesNo

*SMTP batch sending is handled sequentially by the platform, not natively by the provider.

Declaring Capabilities in Custom Providers

When implementing a custom provider, declare capabilities by returning the appropriate values from getCapabilities():

import { EmailProvider, EmailProviderCapabilities, CapabilityEntry } from '@bernierllc/email-sender';

class CustomProvider extends EmailProvider {
  getCapabilities(): EmailProviderCapabilities {
    return {
      supportsBatch: true,
      supportsTemplates: false,
      supportsWebhooks: true,
      supportsAttachments: true,
      supportsCustomHeaders: true,
      supportsDeliveryTracking: true,
      maxBatchSize: 500,
      maxAttachmentSize: 10 * 1024 * 1024, // 10 MB

      // Optional: declare fine-grained features for capability-aware routing
      features: {
        sendEmail: {
          source: 'provider',
          description: 'Send email via custom API',
          degradation: null,
          overridable: false,
        },
        batchSend: {
          source: 'provider',
          description: 'Batch send via custom API bulk endpoint',
          degradation: null,
          overridable: true,
        },
        scheduledSend: {
          source: 'unsupported',
          description: 'Custom provider does not support scheduled send',
          degradation: null,
          overridable: false,
        },
      },
    };
  }
}

When the features map is present, the @bernierllc/email-manager capability router uses it for intelligent provider selection. When absent, the router falls back to the static capability matrix.

Zod Validation

Both CapabilityEntry and DegradationInfo have Zod schemas for runtime validation:

import { capabilityEntrySchema, degradationInfoSchema } from '@bernierllc/email-sender';

const result = capabilityEntrySchema.safeParse(entry);
if (!result.success) {
  console.error('Invalid capability entry:', result.error.issues);
}

Staging Email Safety

Prevent accidental email sends in non-production environments by using the staging safety feature.

Staging with Whitelist (Block Non-Whitelisted)

import { EmailSender } from '@bernierllc/email-sender';

const emailSender = new EmailSender({
  provider: 'smtp' as const,
  host: 'smtp.example.com',
  port: 587,
  username: 'user',
  password: 'pass',
  fromEmail: '[email protected]',
  stagingSafety: {
    enabled: true,
    environment: 'staging',
    whitelist: ['[email protected]', '[email protected]', '[email protected]'],
    blockNonWhitelisted: true,
    logBlocked: true,
  },
});

// This will send (whitelisted)
await emailSender.sendEmail({
  toEmail: '[email protected]',
  subject: 'Test Email',
  htmlContent: '<p>This email will be sent</p>',
});

// This will be blocked (not whitelisted)
const result = await emailSender.sendEmail({
  toEmail: '[email protected]',
  subject: 'Test Email',
  htmlContent: '<p>This email will be blocked</p>',
});
console.log(result.success); // false
console.log(result.errorMessage); // "Email blocked by safety manager: ..."

Staging with Rewrite (Redirect to Test Email)

const emailSender = new EmailSender({
  provider: 'smtp' as const,
  host: 'smtp.example.com',
  port: 587,
  username: 'user',
  password: 'pass',
  fromEmail: '[email protected]',
  stagingSafety: {
    enabled: true,
    environment: 'staging',
    whitelist: ['[email protected]'], // Admin emails go through normally
    rewriteTo: '[email protected]', // All other emails redirected here
    blockNonWhitelisted: false,
    logBlocked: true,
  },
});

// This will be sent to [email protected] instead
await emailSender.sendEmail({
  toEmail: '[email protected]',
  subject: 'Test Email',
  htmlContent: '<p>This will be sent to [email protected]</p>',
});

Production (All Emails Allowed)

const emailSender = new EmailSender({
  provider: 'smtp' as const,
  host: 'smtp.example.com',
  port: 587,
  username: 'user',
  password: 'pass',
  fromEmail: '[email protected]',
  stagingSafety: {
    enabled: true,
    environment: 'production',
    whitelist: [], // Not used in production
    blockNonWhitelisted: false,
    logBlocked: false,
  },
});

// All emails are allowed in production
await emailSender.sendEmail({
  toEmail: '[email protected]',
  subject: 'Production Email',
  htmlContent: '<p>This email will be sent</p>',
});

Check Recipient Safety Before Sending

const safetyResult = emailSender.checkRecipientSafety('[email protected]');
console.log('Safety check result:', safetyResult);

if (safetyResult && !safetyResult.allowed) {
  console.log('Email would be blocked:', safetyResult.reason);
}

Dynamic Configuration Based on Environment

const environment = process.env.NODE_ENV || 'development';

const emailSender = new EmailSender({
  provider: 'smtp' as const,
  host: 'smtp.example.com',
  port: 587,
  username: 'user',
  password: 'pass',
  fromEmail: '[email protected]',
  stagingSafety: {
    enabled: environment !== 'production',
    environment: environment as 'development' | 'staging' | 'production',
    whitelist: environment === 'staging'
      ? ['[email protected]', '[email protected]']
      : [],
    rewriteTo: environment === 'development' ? '[email protected]' : undefined,
    blockNonWhitelisted: environment === 'staging',
    logBlocked: true,
  },
});

console.log('Current environment:', environment);
console.log('Safety enabled:', emailSender.isSafetyEnabled());
console.log('Safety config:', emailSender.getSafetyConfig());

Examples

Send Email with Attachment

import { EmailSender } from '@bernierllc/email-sender';
import fs from 'fs';

const emailSender = new EmailSender(config);

const attachment = {
  filename: 'report.pdf',
  content: fs.readFileSync('report.pdf').toString('base64'),
  contentType: 'application/pdf',
  disposition: 'attachment' as const,
};

const result = await emailSender.sendEmail({
  toEmail: '[email protected]',
  subject: 'Monthly Report',
  htmlContent: '<h1>Monthly Report</h1><p>Please find the attached report.</p>',
  attachments: [attachment],
});

Send Email with Custom Headers

const result = await emailSender.sendEmail({
  toEmail: '[email protected]',
  subject: 'Priority Message',
  htmlContent: '<h1>Important Update</h1>',
  headers: {
    'X-Priority': '1',
    'X-Custom-Header': 'custom-value',
  },
});

Batch Email with Different Content

const emails = [
  {
    toEmail: '[email protected]',
    toName: 'User One',
    subject: 'Personalized Email 1',
    htmlContent: '<h1>Hello User One!</h1>',
  },
  {
    toEmail: '[email protected]',
    toName: 'User Two',
    subject: 'Personalized Email 2',
    htmlContent: '<h1>Hello User Two!</h1>',
  },
];

const results = await emailSender.sendBatch(emails);

Error Handling

try {
  const result = await emailSender.sendEmail(email);
  
  if (result.success) {
    console.log('Email sent successfully');
  } else {
    console.error('Email failed:', result.errorMessage);
    
    // Check if it's a validation error
    const validation = emailSender.validateEmailMessage(email);
    if (!validation.isValid) {
      console.error('Validation errors:', validation.errors);
    }
  }
} catch (error) {
  console.error('Unexpected error:', error);
}

Provider Support

Currently Supported

  • SMTP: Full support with nodemailer
  • SendGrid: Full support with SendGrid API
  • AWS SES: Full support with AWS SDK (requires aws-sdk peer dependency)
  • Mailgun: Full support with Mailgun API
  • Postmark: Full support with Postmark API

Adding New Providers

To add a new provider, extend the EmailProvider base class:

import { EmailProvider, EmailMessage, SendResult } from '@bernierllc/email-sender';

export class CustomProvider extends EmailProvider {
  async sendEmail(email: EmailMessage): Promise<SendResult> {
    // Implement your provider logic here
  }
  
  async sendBatch(emails: EmailMessage[]): Promise<SendResult[]> {
    // Implement batch sending logic
  }
  
  async getDeliveryStatus(messageId: string): Promise<DeliveryStatus | null> {
    // Implement delivery status checking
  }
  
  async processWebhook(payload: any, signature: string): Promise<WebhookEvent[]> {
    // Implement webhook processing
  }
}

Testing

Run the test suite:

npm test

Run tests with coverage:

npm run test:coverage

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests for new functionality
  5. Ensure all tests pass
  6. Submit a pull request

Integration Status

  • Logger integration: not-applicable - Email-sender is a low-level utility that doesn't require @bernierllc/logger integration. Consumer applications should handle logging as needed.
  • Docs-Suite: ready - Complete TypeDoc/JSDoc documentation available. Markdown-based README with extensive examples.
  • NeverHub integration: not-applicable - Email-sender is a stateless utility focused on email delivery. @bernierllc/neverhub-adapter is not needed for this use case.

Graceful Degradation

This package is designed to work standalone without external dependencies beyond the configured email provider. All functionality is self-contained with clear error handling and validation.

License

ISC License - see LICENSE file for details.

Support

For support and questions, please open an issue on GitHub or contact Bernier LLC.

Per-message analytics parameters

SendGrid supports explicit per-message UTM values through the existing tracking object:

tracking: {
  utm: true,
  utmParameters: {
    source: 'newsletter', medium: 'email', campaign: 'launch',
    term: '', content: 'hero',
  },
}

Parameters are emitted only with utm: true. utm: false sends only ganalytics.enable: false; omitted utm retains account behavior. Omitted/null parameter fields leave account values in control; empty strings are transmitted exactly. Enabled parameter values other than strings/null throw EmailValidationError before HTTP. The library does not invent a provider length limit. Providers whose utmTracking capability is unsupported ignore these parameters.

Click and open tracking remain independently configurable. The payload contract does not guarantee a particular provider's delivered-link handling of empty values or analytics with click tracking disabled; validate those combinations against the account used for delivery before offering them as application behavior.

版本与发布记录

@bernierllc/email-sender 共发布 22 个版本,首次发布于 2025/07/29;过去 12 个月发布了 17 个版本。

版本发布日期距今
5.14.0latest2026/11/053 天前
5.13.02026/10/2415 天前
5.12.02026/10/2316 天前
5.11.02026/10/1920 天前
5.10.02026/10/021 个月前
5.9.02026/09/301 个月前

关于 @bernierllc/email-sender 的常见问题

@bernierllc/email-sender 是什么?

Email sending with provider-agnostic interface and SendGrid advanced features (templates, verified senders, rate limiting)

如何安装 @bernierllc/email-sender?

在项目目录运行 npm install @bernierllc/email-sender,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:aws-sdk(^2.1000.0)、nodemailer(^6.9.0)、@sendgrid/mail(^8.1.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@bernierllc/email-sender 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@bernierllc/email-sender 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@bernierllc/email-sender 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@bernierllc/email-sender 可以商用吗?

@bernierllc/email-sender 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@bernierllc/email-sender 的下载量有多少?

根据 npm 官方下载统计,@bernierllc/email-sender 在 2026/10/08 – 2026/11/06 这 30 天内被下载 2,498 次,日均约 83 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@bernierllc/email-sender 还在维护吗?

最新版本 5.14.0 发布于 2026/11/05(3 天前);至今共发布 22 个版本,其中过去 12 个月发布了 17 个;首次发布于 2025/07/29。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。