@bernierllc/email-manager
v0.6.0
Published
Complete email management suite with template management, scheduling, analytics, and provider orchestration (Node.js only - use @bernierllc/email-manager-client for browser)
Downloads
486
Readme
@bernierllc/email-manager
A comprehensive email management service that orchestrates email sending, template management, scheduling, and analytics. This service integrates with @bernierllc/email-sender, @bernierllc/template-engine, and @bernierllc/magic-link to provide a complete email solution.
⚠️ Node.js Only
This package is Node.js-only and cannot be bundled for browser use due to Node.js dependencies (nodemailer, etc.).
For browser/frontend applications, use @bernierllc/email-manager-client instead, which provides the same API via HTTP calls.
Features
🚀 Email Orchestration
- Multi-provider support with automatic failover
- Template-based emails with dynamic content rendering
- Email scheduling with retry logic
- Batch email processing for bulk operations
📝 Template Management
- CRUD operations for email templates
- Template versioning and lifecycle management
- Variable validation and syntax checking
- Category-based organization
🔧 Provider Management
- Multi-provider configuration (SendGrid, Mailgun, AWS SES, SMTP, Postmark)
- Provider health monitoring and status tracking
- Rate limiting and usage tracking
- Automatic failover between providers
📊 Analytics & Reporting
- Email performance tracking (opens, clicks, bounces)
- Delivery reports and bounce analysis
- Provider performance metrics
- Historical data and trend analysis
⏰ Scheduling System
- Queue-based scheduling with precise timing
- Retry logic for failed deliveries
- Schedule management (cancel, modify)
- Background processing with automatic cleanup
Installation
npm install @bernierllc/email-managerDependencies
This package requires the following BernierLLC core packages:
npm install @bernierllc/email-sender @bernierllc/template-engine @bernierllc/magic-linkQuick Start
import { EmailManager, EmailManagerConfig } from '@bernierllc/email-manager';
// Configure providers
const config: EmailManagerConfig = {
providers: [
{
id: 'sendgrid',
name: 'SendGrid',
type: 'sendgrid',
config: { apiKey: process.env.SENDGRID_API_KEY },
isActive: true,
priority: 1
}
],
analytics: { enabled: true },
scheduling: { enabled: true }
};
// Initialize manager
const emailManager = new EmailManager(config);
// Send email
const result = await emailManager.sendEmail({
to: '[email protected]',
subject: 'Hello World',
html: '<h1>Hello!</h1><p>This is a test email.</p>',
text: 'Hello! This is a test email.'
});
console.log('Email sent:', result.success);Core API
Email Operations
Send Email
const result = await emailManager.sendEmail({
to: ['[email protected]', '[email protected]'],
cc: ['[email protected]'],
subject: 'Important Update',
html: '<h1>Update</h1><p>Important information...</p>',
text: 'Update: Important information...',
priority: 'high',
metadata: { campaign: 'newsletter' }
});Send Templated Email
const result = await emailManager.sendTemplatedEmail(
'welcome-email',
{
user: { name: 'John Doe' },
company: 'MyApp',
confirmUrl: 'https://myapp.com/confirm?token=abc123'
},
['[email protected]']
);Schedule Email
const scheduleTime = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
const result = await emailManager.scheduleEmail({
to: '[email protected]',
subject: 'Reminder',
html: '<p>This is your scheduled reminder</p>'
}, scheduleTime);Template Management
Create Template
const template = await emailManager.createTemplate({
id: 'welcome-email',
name: 'Welcome Email',
subject: 'Welcome {{ user.name }}!',
htmlTemplate: '<h1>Welcome {{ user.name }}!</h1><p>Thanks for joining {{ company }}!</p>',
textTemplate: 'Welcome {{ user.name }}! Thanks for joining {{ company }}!',
variables: [
{ name: 'user.name', type: 'string', required: true },
{ name: 'company', type: 'string', required: true }
],
category: 'onboarding',
version: '1.0.0',
isActive: true
});List Templates
const templates = await emailManager.listTemplates({
page: 1,
pageSize: 20,
category: 'onboarding',
active: true,
search: 'welcome'
});Update Template
const result = await emailManager.updateTemplate('welcome-email', {
subject: 'Welcome to our platform, {{ user.name }}!',
isActive: false
});Provider Management
Add Provider
const result = await emailManager.addProvider({
id: 'mailgun-backup',
name: 'Mailgun Backup',
type: 'mailgun',
config: {
apiKey: process.env.MAILGUN_API_KEY,
domain: process.env.MAILGUN_DOMAIN
},
isActive: true,
priority: 2,
rateLimit: {
maxPerMinute: 60,
maxPerHour: 1000,
maxPerDay: 10000
}
});Get Provider Status
const status = await emailManager.getProviderStatus('sendgrid');
console.log('Provider health:', status.isHealthy);
console.log('Current usage:', status.currentUsage);Test Provider Connection
const testResult = await emailManager.testConnection('sendgrid');
console.log('Connection test:', testResult.success, testResult.message);Analytics & Reporting
Get Email Statistics
const stats = await emailManager.getEmailStats('email-message-id');
console.log('Delivery rate:', stats.deliveryRate);
console.log('Open rate:', stats.openRate);
console.log('Click rate:', stats.clickRate);Get Delivery Report
const report = await emailManager.getDeliveryReport({
startDate: new Date('2024-01-01'),
endDate: new Date('2024-01-31'),
provider: 'sendgrid'
});
console.log('Total sent:', report.totalSent);
console.log('Delivery rate:', report.deliveryRate);Get Analytics Summary
const summary = await emailManager.getAnalyticsSummary(30); // Last 30 days
console.log('Overview:', summary.overview);
console.log('Top providers:', summary.topProviders);
console.log('Daily stats:', summary.dailyStats);Scheduling Management
Cancel Scheduled Email
const result = await emailManager.cancelScheduledEmail('schedule-id');
console.log('Cancelled:', result.success);Configuration
EmailManagerConfig
interface EmailManagerConfig {
providers: EmailProvider[]; // Email provider configurations
templates?: EmailTemplate[]; // Pre-loaded templates
scheduling?: SchedulingConfig; // Scheduler settings
analytics?: AnalyticsConfig; // Analytics settings
retry?: RetryConfig; // Retry policy settings
}Provider Configuration
interface EmailProvider {
id: string; // Unique provider ID
name: string; // Display name
type: 'sendgrid' | 'mailgun' | 'ses' | 'smtp' | 'postmark';
config: ProviderConfig; // Provider-specific config
isActive: boolean; // Enable/disable provider
priority: number; // Provider priority (lower = higher priority)
rateLimit?: RateLimit; // Rate limiting settings
}Template Configuration
interface EmailTemplate {
id: string; // Unique template ID
name: string; // Display name
subject: string; // Email subject (supports variables)
htmlTemplate: string; // HTML template content
textTemplate?: string; // Plain text template content
variables: TemplateVariable[]; // Template variables
category?: string; // Template category
version: string; // Template version
isActive: boolean; // Enable/disable template
}Provider Types
SendGrid
{
type: 'sendgrid',
config: {
apiKey: 'your-sendgrid-api-key'
}
}Mailgun
{
type: 'mailgun',
config: {
apiKey: 'your-mailgun-api-key',
domain: 'your-domain.mailgun.org'
}
}AWS SES
{
type: 'ses',
config: {
accessKeyId: 'your-access-key',
secretAccessKey: 'your-secret-key',
region: 'us-east-1'
}
}SMTP
{
type: 'smtp',
config: {
host: 'smtp.gmail.com',
port: 587,
secure: false,
auth: {
user: '[email protected]',
pass: 'your-password'
}
}
}Postmark
{
type: 'postmark',
config: {
serverToken: 'your-postmark-server-token'
}
}Template Variables
Templates support dynamic content through variables:
<!-- HTML Template -->
<h1>Welcome {{ user.name }}!</h1>
<p>Your account with {{ company }} is ready.</p>
<a href="{{ confirmUrl }}">Confirm Email</a>
<!-- Text Template -->
Welcome {{ user.name }}!
Your account with {{ company }} is ready.
Confirm your email: {{ confirmUrl }}Variables are defined in the template configuration:
variables: [
{ name: 'user.name', type: 'string', required: true },
{ name: 'company', type: 'string', required: true },
{ name: 'confirmUrl', type: 'string', required: true }
]Error Handling
The email manager provides comprehensive error handling:
const result = await emailManager.sendEmail(emailData);
if (!result.success) {
console.error('Failed to send email:');
result.errors?.forEach(error => {
console.error(`${error.code}: ${error.message}`);
if (error.recipient) {
console.error(`Recipient: ${error.recipient}`);
}
});
}Event Tracking
Track email events for analytics:
// Track email opened (typically from email tracking pixel)
emailManager.trackEmailEvent('opened', 'email-id', '[email protected]');
// Track email clicked (typically from link tracking)
emailManager.trackEmailEvent('clicked', 'email-id', '[email protected]', {
url: 'https://example.com/clicked-link'
});
// Track email bounced (typically from webhook)
emailManager.trackEmailEvent('bounced', 'email-id', '[email protected]', {
reason: 'Invalid email address'
});Webhook Integration
Set up webhooks with your email providers to track events:
// Express.js webhook endpoint example
app.post('/webhook/sendgrid', (req, res) => {
const events = req.body;
events.forEach(event => {
const { event: eventType, email, sg_message_id } = event;
switch (eventType) {
case 'delivered':
emailManager.trackEmailEvent('delivered', sg_message_id, email);
break;
case 'open':
emailManager.trackEmailEvent('opened', sg_message_id, email);
break;
case 'click':
emailManager.trackEmailEvent('clicked', sg_message_id, email, { url: event.url });
break;
case 'bounce':
emailManager.trackEmailEvent('bounced', sg_message_id, email, { reason: event.reason });
break;
}
});
res.status(200).send('OK');
});Magic Link Integration
Generate secure authentication links:
// Generate magic link
const magicLink = await emailManager.generateMagicLink(
'[email protected]',
'login',
{ userId: '123', redirectUrl: '/dashboard' }
);
// Send magic link email
await emailManager.sendTemplatedEmail('magic-link-email', {
magicLink: magicLink.url,
user: { email: '[email protected]' }
}, ['[email protected]']);
// Verify magic link (in your authentication endpoint)
const verification = await emailManager.verifyMagicLink(token);
if (verification.valid) {
// Log user in
console.log('User authenticated:', verification.payload);
}Performance Optimization
Template Caching
Templates are cached after compilation for better performance.
Provider Pooling
Email providers reuse connections where possible.
Batch Processing
Process multiple emails efficiently:
const emails = [
{ to: '[email protected]', subject: 'Email 1', html: '<p>Content 1</p>' },
{ to: '[email protected]', subject: 'Email 2', html: '<p>Content 2</p>' },
// ... more emails
];
const results = await Promise.all(
emails.map(email => emailManager.sendEmail(email))
);Rate Limiting
Configure rate limits per provider to respect API limits:
{
rateLimit: {
maxPerMinute: 100,
maxPerHour: 1000,
maxPerDay: 10000
}
}System Status
Get comprehensive system statistics:
const stats = emailManager.getSystemStats();
console.log('Templates:', stats.templates);
// { total: 5, active: 4, inactive: 1, byCategory: { onboarding: 2, marketing: 3 } }
console.log('Providers:', stats.providers);
// { total: 2, active: 2, inactive: 0, byType: { sendgrid: 1, mailgun: 1 } }
console.log('Scheduler:', stats.scheduler);
// { isRunning: true, totalInQueue: 3, scheduled: 3, overdue: 0 }Shutdown
Always properly shutdown the email manager:
emailManager.shutdown();This stops the scheduler and cleans up resources.
Enhanced Features
The EnhancedEmailManager extends the base EmailManager with calendar invites, batch sending, subscription management, unsubscribe flows, and more. All enhanced features are available through a single import:
import { EnhancedEmailManager, EnhancedEmailManagerConfig } from '@bernierllc/email-manager';Calendar Invites
Send calendar invitations (ICS) and cancellations as email attachments. The manager generates valid ICS content using the @bernierllc/email-calendar package and attaches it automatically.
Send a Calendar Invite
import { EnhancedEmailManager, CalendarEvent } from '@bernierllc/email-manager';
const event: CalendarEvent = {
uid: '[email protected]',
title: 'Q2 Business Review',
description: 'Quarterly review of business metrics.',
start: new Date('2025-07-15T14:00:00Z'),
end: new Date('2025-07-15T15:30:00Z'),
location: 'Conference Room A',
organizer: { email: '[email protected]', name: 'Alice Johnson' },
attendees: [
{ email: '[email protected]', name: 'Bob Smith', role: 'REQ-PARTICIPANT' },
{ email: '[email protected]', name: 'Carol Lee', role: 'OPT-PARTICIPANT' },
],
};
const result = await manager.sendCalendarInvite(
event,
['[email protected]', '[email protected]'],
);
console.log('Invite sent:', result.success);You can customize the subject and body:
const result = await manager.sendCalendarInvite(event, recipients, {
subject: 'You are invited: Q2 Business Review',
htmlBody: '<h2>Q2 Review</h2><p>Please join us for the quarterly review.</p>',
textBody: 'Please join us for the Q2 quarterly review.',
metadata: { campaign: 'internal-meetings' },
});Cancel a Calendar Event
import { CalendarAttendee } from '@bernierllc/email-manager';
const organizer: CalendarAttendee = { email: '[email protected]', name: 'Alice' };
const attendees: CalendarAttendee[] = [
{ email: '[email protected]', name: 'Bob' },
{ email: '[email protected]', name: 'Carol' },
];
const result = await manager.sendCalendarCancel(
'[email protected]', // UID of the original event
organizer,
attendees,
{
subject: 'Cancelled: Q2 Business Review',
htmlBody: '<p>The Q2 Business Review has been cancelled.</p>',
},
);See
examples/calendar-invites.tsfor a complete working example.
Batch Sending
Send large volumes of email with built-in concurrency control, rate limiting, retry logic, and progress tracking. Powered by @bernierllc/email-batch-sender.
Basic Batch Send
import { BatchEmailInput } from '@bernierllc/email-manager';
const emails: BatchEmailInput[] = [
{
toEmail: '[email protected]',
subject: 'Newsletter - July',
htmlContent: '<h1>Newsletter</h1><p>Hello Alice...</p>',
textContent: 'Newsletter\n\nHello Alice...',
},
{
toEmail: '[email protected]',
subject: 'Newsletter - July',
htmlContent: '<h1>Newsletter</h1><p>Hello Bob...</p>',
},
];
const result = await manager.sendBatch(emails);
console.log(`Sent: ${result.succeeded}/${result.total}, Failed: ${result.failed}`);Batch Send with Rate Limiting and Retry
import { BatchSenderOptions } from '@bernierllc/email-manager';
const options: BatchSenderOptions = {
concurrency: 5, // Up to 5 emails in parallel
rateLimit: {
maxPerSecond: 10, // Respect provider rate limits
},
retry: {
maxRetries: 3, // Retry failed sends
initialDelayMs: 1000, // 1 second initial delay
backoffMultiplier: 2, // Exponential backoff
},
failureStrategy: 'continue', // Keep sending even if some fail
};
const result = await manager.sendBatch(emails, options);Batch Send with Progress Tracking
const result = await manager.sendBatch(emails, {
concurrency: 3,
onProgress: (progress) => {
const pct = ((progress.completed / progress.total) * 100).toFixed(0);
console.log(`Progress: ${pct}% - ${progress.succeeded} sent, ${progress.failed} failed`);
},
});Abort on Failure
For critical emails where partial delivery is unacceptable:
const result = await manager.sendBatch(criticalEmails, {
concurrency: 1,
failureStrategy: 'abort', // Stop immediately on first failure
retry: { maxRetries: 5 },
});
if (result.aborted) {
console.error('Batch was aborted due to a failure');
}See
examples/batch-sending.tsfor complete examples.
Subscription Management
Manage subscriber lists, add/remove subscribers, and handle bulk imports. The subscription system uses @bernierllc/email-subscription under the hood.
Create Subscriber Lists
const newsletter = await manager.createSubscriberList('Monthly Newsletter', {
description: 'Monthly product newsletter subscribers',
metadata: { category: 'marketing' },
});
console.log('List ID:', newsletter.id);Add Subscribers
// Individual subscriber
await manager.addSubscriber(newsletter.id, {
email: '[email protected]',
name: 'Alice Johnson',
});
// Bulk add (skips duplicates and suppressed emails)
const result = await manager.addSubscribers(newsletter.id, [
{ email: '[email protected]', name: 'User One' },
{ email: '[email protected]', name: 'User Two' },
{ email: '[email protected]', name: 'User Three' },
]);
console.log(`Added: ${result.added}, Skipped: ${result.skipped}`);Look Up and Remove Subscribers
// Look up a subscriber
const subscriber = await manager.getSubscriber(newsletter.id, '[email protected]');
if (subscriber) {
console.log('Status:', subscriber.status);
}
// Remove a subscriber
const removed = await manager.removeSubscriber(newsletter.id, '[email protected]');Delete a List
const deleted = await manager.deleteSubscriberList(newsletter.id);Unsubscribe Flows
Generate signed unsubscribe URLs and process unsubscribe requests. Requires the subscription.unsubscribe configuration.
Configuration
const config: EnhancedEmailManagerConfig = {
providers: [/* ... */],
subscription: {
enabled: true,
unsubscribe: {
baseUrl: 'https://example.com/unsubscribe',
secret: process.env.UNSUBSCRIBE_SECRET!,
expiresIn: 30 * 24 * 60 * 60, // 30 days
},
},
};Generate Unsubscribe URL
const url = manager.generateUnsubscribeUrl('[email protected]', listId);
// Returns: https://example.com/unsubscribe?token=<signed-jwt>Process Unsubscribe Request
In your web server's unsubscribe endpoint:
app.get('/unsubscribe', async (req, res) => {
const token = req.query.token as string;
try {
await manager.processUnsubscribe(token);
res.send('You have been unsubscribed successfully.');
} catch (error) {
res.status(400).send('Invalid or expired unsubscribe link.');
}
});Send Email with List-Unsubscribe Headers
Automatically generate and attach RFC 2369 / RFC 8058 List-Unsubscribe headers:
const result = await manager.sendWithUnsubscribe(
{
to: '[email protected]',
subject: 'Monthly Newsletter',
html: '<p>Newsletter content...</p>',
},
listId, // Subscriber list ID
'[email protected]', // Recipient for token generation
true, // Enable one-click unsubscribe (RFC 8058)
);See
examples/subscription-management.tsfor the full lifecycle.
Suppression
Check if an email address is suppressed before sending. Emails become suppressed when a subscriber unsubscribes or when bounces/complaints are processed.
// Check global suppression
const isSuppressed = await manager.isSuppressed('[email protected]');
// Check list-specific suppression
const isListSuppressed = await manager.isSuppressed('[email protected]', listId);
if (isSuppressed) {
console.log('Skipping send - email is suppressed');
}Custom Subscription Store
By default, the manager uses an in-memory subscription store. For production, replace it with a database-backed implementation:
import { SubscriptionStore } from '@bernierllc/email-manager';
class DatabaseSubscriptionStore implements SubscriptionStore {
// Implement all SubscriptionStore methods using your database
// (createList, getList, addSubscriber, etc.)
}
const store = new DatabaseSubscriptionStore();
manager.setSubscriptionStore(store);Custom Headers
Add custom email headers for threading, read receipts, and other purposes by including them in the metadata.headers field.
Email Threading
const result = await manager.sendEmail({
to: '[email protected]',
subject: 'Re: Project Discussion',
html: '<p>I agree with the proposed approach.</p>',
metadata: {
headers: {
'In-Reply-To': '<[email protected]>',
'References': '<[email protected]>',
},
},
});List-Unsubscribe Headers (Standalone)
Use the createListUnsubscribeHeaders utility directly:
import { createListUnsubscribeHeaders } from '@bernierllc/email-manager';
const headers = createListUnsubscribeHeaders(
'https://example.com/unsubscribe?id=123', // HTTPS unsubscribe URL
'mailto:[email protected]?subject=unsub', // Optional mailto fallback
true, // One-click unsubscribe (RFC 8058)
);
const result = await manager.sendEmail({
to: '[email protected]',
subject: 'Weekly Digest',
html: '<p>Your digest...</p>',
metadata: { headers },
});MDN (Read Receipt) Request
const result = await manager.sendEmail({
to: '[email protected]',
subject: 'Contract for Review',
html: '<p>Please confirm receipt of the attached contract.</p>',
metadata: {
headers: {
'Disposition-Notification-To': '[email protected]',
},
},
});See
examples/attachments-and-headers.tsfor complete examples.
Attachments
Send emails with file attachments, inline images, or both.
File Attachment
const result = await manager.sendEmail({
to: '[email protected]',
subject: 'Report Attached',
html: '<p>Please find the report attached.</p>',
attachments: [
{
filename: 'report.pdf',
content: fs.readFileSync('/path/to/report.pdf'),
contentType: 'application/pdf',
},
],
});String Content Attachment
const csvData = 'Name,Email\nAlice,[email protected]\nBob,[email protected]\n';
const result = await manager.sendEmail({
to: '[email protected]',
subject: 'User Export',
html: '<p>User export attached.</p>',
attachments: [
{
filename: 'users.csv',
content: csvData, // String content is also supported
contentType: 'text/csv',
},
],
});Inline / Embedded Image
Reference inline images using a Content-ID (CID):
const result = await manager.sendEmail({
to: '[email protected]',
subject: 'Order Confirmation',
html: `
<img src="cid:company-logo" alt="Logo" />
<h2>Order Confirmed!</h2>
`,
attachments: [
{
filename: 'logo.png',
content: fs.readFileSync('/path/to/logo.png'),
contentType: 'image/png',
contentDisposition: 'inline',
cid: 'company-logo', // Matches src="cid:company-logo" in HTML
},
],
});Multiple Attachments
const result = await manager.sendEmail({
to: '[email protected]',
subject: 'Brand Kit Assets',
html: '<p>Attached are the brand assets.</p>',
attachments: [
{
filename: 'guidelines.pdf',
content: fs.readFileSync('/path/to/guidelines.pdf'),
contentType: 'application/pdf',
},
{
filename: 'logo.png',
content: fs.readFileSync('/path/to/logo.png'),
contentType: 'image/png',
},
{
filename: 'palette.json',
content: JSON.stringify({ primary: '#0066CC' }, null, 2),
contentType: 'application/json',
},
],
});See
examples/attachments-and-headers.tsfor complete examples.
Capability Matrix & Smart Routing
The email manager includes a full capability matrix that maps every feature to every provider, enabling smart routing decisions and graceful degradation.
Capability Matrix
Each feature is classified per provider with one of four source types:
| Source | Meaning |
|--------|---------|
| provider | Natively supported by the email provider's API |
| platform | Implemented by the platform (polyfilled locally) |
| enhanced | Built on top of provider primitives with added value |
| unsupported | Not available for this provider |
Provider Feature Support
| Feature | SendGrid | Mailgun | Postmark | SES | SMTP | |---------|----------|---------|----------|-----|------| | sendEmail | provider | provider | provider | provider | provider | | batchSend | provider | provider | provider | provider | platform | | providerTemplates | provider | provider | provider | provider | unsupported | | localTemplates | platform | platform | platform | platform | platform | | calendarInvites | platform | platform | platform | platform | platform | | calendarEventMgmt | platform | platform | platform | platform | platform | | webhooksReceive | provider | provider | provider | provider | unsupported | | webhookNormalization | enhanced | enhanced | enhanced | enhanced | unsupported | | deliveryTracking | provider | provider | provider | provider | unsupported | | inboundEmailParsing | enhanced | enhanced | enhanced | enhanced | unsupported | | subscriptionMgmt | enhanced | enhanced | platform | platform | platform | | suppressionLists | provider | provider | provider | provider | platform | | unsubscribeUrlGeneration | platform | platform | platform | platform | platform | | scheduledSend | provider | provider | platform | platform | platform | | openClickTracking | provider | provider | provider* | unsupported | unsupported | | emailContentParsing | platform | platform | platform | platform | platform | | emailHeaderMgmt | platform | platform | platform | platform | platform | | attachments | provider | provider | provider | provider | provider | | advancedAttachmentProcessing | platform | platform | platform | platform | platform | | dkimSpf | provider | provider | provider | provider | unsupported | | linkBranding | provider | provider | unsupported | unsupported | unsupported | | retryResilience | platform | platform | platform | platform | platform | | multiProviderFailover | platform | platform | platform | platform | platform |
*Postmark supports open tracking only; click tracking is not available.
Notable provider-specific limitations:
- SendGrid
scheduledSend: 72-hour maximum scheduling window - Mailgun
scheduledSend: 3-day maximum scheduling window - SES
webhooksReceive/deliveryTracking: Requires SNS topic configuration
Routing Configuration
Capability-aware routing is opt-in. Enable it by adding a routing section to your config:
import { EmailManager, EmailManagerConfig } from '@bernierllc/email-manager';
const config: EmailManagerConfig = {
providers: [
{ id: 'sg', name: 'SendGrid', type: 'sendgrid', config: { apiKey: '...' }, isActive: true, priority: 1 },
{ id: 'mg', name: 'Mailgun', type: 'mailgun', config: { apiKey: '...', domain: '...' }, isActive: true, priority: 2 },
],
routing: {
strategy: 'primary-failover',
featureOverrides: {
// Always use platform implementation for local templates
localTemplates: { behavior: 'platform-always' },
// Throw an error if no native provider supports the feature
openClickTracking: { behavior: 'error' },
},
},
degradation: {
logLevel: 'warn',
includeDocLinks: true,
docsBaseUrl: 'https://docs.example.com/email',
},
};
const manager = new EmailManager(config);Feature Override Behaviors
| Behavior | Description |
|----------|-------------|
| native-first | (Default) Prefer providers with native or enhanced support; fall back to platform if none found |
| platform-always | Always use the platform implementation, even if the provider supports it natively |
| error | Throw FeatureUnsupportedError if no provider has native or enhanced support |
Degradation Behavior
When a feature is routed to a provider via platform polyfill (not native), the system records degradation information:
platformsource features (e.g., SMTPbatchSend): Emails are sent sequentially rather than in a true batch. TheSendResult.routingfield indicatesdegraded: truewith adegradationReason.unsupportedfeatures (e.g., SMTPwebhooksReceive): The router skips the provider entirely and tries the next in priority order. If no provider supports the feature, aFeatureUnsupportedErroris thrown.- Degradation logging: Controlled by
degradation.logLevel('silent'|'info'|'warn'|'error'). WhenincludeDocLinksistrue, log messages include links to degradation documentation.
Redis Resolver Setup
By default, the capability matrix is resolved from an in-memory snapshot. For shared or dynamic capability data across multiple instances, use the Redis-backed resolver:
# Set the environment variable to enable Redis resolver auto-detection
export EMAIL_MANAGER_REDIS_URL="redis://localhost:6379"The factory (createCapabilityResolver) automatically detects the environment variable and creates a SafeCapabilityResolver that wraps a Redis resolver with an in-memory fallback. If ioredis is not installed or Redis is unavailable, it falls back to the in-memory resolver silently.
Redis Resolver Options
import { createCapabilityResolver } from '@bernierllc/email-manager';
const resolver = await createCapabilityResolver({
namespace: 'myapp:capabilities', // Redis key namespace (default: 'email-manager:capabilities')
ttl: 3600, // Cache TTL in seconds (default: varies by implementation)
});You can also provide your own resolver implementation:
const config: EmailManagerConfig = {
providers: [/* ... */],
routing: {
strategy: 'primary-failover',
capabilityResolver: myCustomResolver, // Must implement CapabilityResolver interface
},
};RoutingMetadata in Responses
When routing is active, SendResult includes a routing field:
const result = await manager.sendEmail(emailData);
if (result.routing) {
console.log('Provider used:', result.routing.provider);
console.log('Feature:', result.routing.feature);
console.log('Source:', result.routing.source); // 'provider' | 'platform' | 'enhanced' | 'unsupported'
console.log('Degraded:', result.routing.degraded); // true if platform polyfill was used
console.log('Reason:', result.routing.degradationReason);
console.log('Tried:', result.routing.attemptedProviders); // providers checked before selecting
}The RoutingMetadata interface:
interface RoutingMetadata {
provider: string;
feature: string;
source: 'provider' | 'platform' | 'enhanced' | 'unsupported';
degraded: boolean;
degradationReason?: string;
attemptedProviders?: string[];
}Migration Guide from v0.4.x
The capability matrix and routing system is fully backward compatible. Existing code continues to work without changes:
- No routing config: When
routingis omitted from the config, the manager uses the same provider selection logic as before (priority-based failover). - No degradation config: When
degradationis omitted, degradation events are not logged. - Opt-in: To enable capability-aware routing, add a
routingsection to your config. TheRoutingMetadatafield onSendResultonly appears when routing is active. - No new required dependencies: The Redis resolver is optional and only activated when
EMAIL_MANAGER_REDIS_URLis set andioredisis installed.
Re-exported Types
For convenience, the email-manager re-exports types and utilities from its sub-packages so consumers do not need direct dependencies:
| Package | Re-exported Items |
|---------|-------------------|
| @bernierllc/email-calendar | CalendarEvent, CalendarAttendee, CalendarMethod, createCalendarInvite, createCalendarCancel, toCalendarAttachment |
| @bernierllc/email-batch-sender | BatchSenderOptions, BatchResult, BatchEmailInput, BatchProgress, BatchSender, createBatchSender |
| @bernierllc/email-subscription | SubscriberList, Subscriber, SubscriptionStore, AddSubscriberInput, CreateListOptions, BulkAddResult, UnsubscribeManager, SuppressionManager, InMemorySubscriptionStore |
| @bernierllc/email-headers | EmailHeaders, ThreadingHeaders, ListUnsubscribeHeaders, createListUnsubscribeHeaders |
| @bernierllc/email-attachments | AttachmentInput, AttachmentLimits, AttachmentValidationResult |
License
Copyright (c) 2025 Bernier LLC. All rights reserved.
See Also
- @bernierllc/email-sender - Core email sending functionality
- @bernierllc/template-engine - Template rendering engine
- @bernierllc/magic-link - Secure token generation
