@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-servicePeer Dependencies
Install email provider(s) you need:
# SendGrid
npm install @sendgrid/mail
# SMTP (included)
npm install nodemailerUsage
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 occurredRetry 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, andretry-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 emailsendBulk(requests: EmailRequest[]): Promise<SendEmailResult[]>- Send multiple emails
Template Management
createTemplate(request: TemplateCreateRequest): Promise<EmailTemplate>- Create templategetTemplate(id: string): Promise<EmailTemplate | null>- Get templateupdateTemplate(id: string, update: TemplateUpdateRequest): Promise<EmailTemplate>- Update templatedeleteTemplate(id: string): Promise<boolean>- Delete template
Delivery Tracking
getDelivery(id: string): Promise<DeliveryRecord | null>- Get delivery recordtrackOpen(deliveryId: string): Promise<void>- Track email opentrackClick(deliveryId: string): Promise<void>- Track link clickgetDeliveryStats(startDate?: Date, endDate?: Date): Promise<DeliveryStats>- Get statistics
Subscriber Management
createSubscriber(request: SubscriberCreateRequest): Promise<Subscriber>- Create subscribergetSubscriber(id: string): Promise<Subscriber | null>- Get subscriberupdateSubscriber(id: string, update: SubscriberUpdateRequest): Promise<Subscriber>- Update subscriberunsubscribe(email: string): Promise<boolean>- Unsubscribe emailcreateList(name: string, description?: string): Promise<SubscriberList>- Create list
Webhook Handling
registerWebhookHandler(event: string, handler: WebhookHandler): void- Register handlerhandleWebhook(event: WebhookEvent): Promise<void>- Process webhook
Lifecycle
initialize(): Promise<void>- Initialize serviceshutdown(): 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 minuteDependencies
This package orchestrates:
- @bernierllc/email-sender - Multi-provider email delivery
- @bernierllc/email-parser - Email content parsing
- @bernierllc/template-engine - Template rendering
- @bernierllc/queue-manager - Background job processing
- @bernierllc/webhook-validator - Webhook verification
- @bernierllc/database-adapter - Data persistence
- @bernierllc/logger - Structured logging
- @bernierllc/config-manager - Configuration management
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 sendingtemplates.ts- Template management and renderingtracking.ts- Delivery tracking and analyticssubscribers.ts- Subscriber managementwebhooks.ts- Webhook handlingmulti-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.
