@bernierllc/email-sender
v5.0.0
Published
Email sending with provider-agnostic interface and SendGrid advanced features (templates, verified senders, rate limiting)
Readme
@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-senderNote: This package uses peer dependencies. You must install them separately:
# For SMTP provider
npm install nodemailer
# For AWS SES provider
npm install aws-sdkQuick 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',
};
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>;
}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;
}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-sdkpeer 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 testRun tests with coverage:
npm run test:coverageContributing
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Ensure all tests pass
- 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.
