@454creative/easy-email
Version:
A framework-agnostic email service library for Node.js with observability and monitoring
142 lines (124 loc) • 4.59 kB
text/typescript
import nodemailer, { Transporter } from 'nodemailer';
import sgMail from '@sendgrid/mail';
import { SESClient } from '@aws-sdk/client-ses';
import { fromEnv } from '@aws-sdk/credential-providers';
import {
EmailProviderType,
EmailProviderConfig,
SmtpConfig,
SendGridConfig,
SesConfig,
} from '../interfaces/email-options.interface';
import { ConfigurationError } from '../errors';
/**
* Factory for creating email provider configurations
*/
export class ProviderFactory {
/**
* Create SMTP transporter
*/
static createSmtpTransporter(config: SmtpConfig): Transporter {
return nodemailer.createTransport(config);
}
/**
* Configure SendGrid
*/
static configureSendGrid(config: SendGridConfig): void {
sgMail.setApiKey(config.apiKey);
}
/**
* Create AWS SES client
*/
static createSesClient(config: SesConfig): SESClient {
const clientConfig: any = {
region: config.region,
apiVersion: config.apiVersion || '2010-12-01',
maxAttempts: config.maxRetries || 3,
};
// Configure AWS credentials
if (config.credentials) {
clientConfig.credentials = config.credentials;
} else if (config.accessKeyId && config.secretAccessKey) {
clientConfig.credentials = {
accessKeyId: config.accessKeyId,
secretAccessKey: config.secretAccessKey,
sessionToken: config.sessionToken,
};
} else {
// Use AWS credential providers chain (environment variables, IAM roles, etc.)
clientConfig.credentials = fromEnv();
}
// Configure AWS SES endpoint if provided
if (config.endpoint) {
clientConfig.endpoint = config.endpoint;
}
// Configure AWS SES HTTP options
if (config.httpOptions) {
clientConfig.requestHandler = {
httpOptions: config.httpOptions,
};
}
return new SESClient(clientConfig);
}
/**
* Create provider configuration from legacy SMTP config
*/
static createProviderConfig(config: SmtpConfig | EmailProviderConfig): EmailProviderConfig {
if (!('type' in config)) {
return {
type: EmailProviderType.SMTP,
config: config as SmtpConfig,
};
}
return config as EmailProviderConfig;
}
/**
* Validate provider configuration
*/
static validateProviderConfig(config: EmailProviderConfig): void {
if (!config.type) {
throw new ConfigurationError('Provider type is required');
}
if (!config.config) {
throw new ConfigurationError('Provider configuration is required');
}
switch (config.type) {
case EmailProviderType.SMTP:
const smtpConfig = config.config as SmtpConfig;
if (!smtpConfig.host || !smtpConfig.port) {
throw new ConfigurationError('SMTP host and port are required');
}
break;
case EmailProviderType.SENDGRID:
const sendGridConfig = config.config as SendGridConfig;
if (!sendGridConfig.apiKey) {
throw new ConfigurationError('SendGrid API key is required');
}
// Check for basic API key format (SendGrid keys typically start with "SG.")
if (typeof sendGridConfig.apiKey === 'string' && !sendGridConfig.apiKey.startsWith('SG.')) {
throw new ConfigurationError('SendGrid API key appears to be invalid. Valid SendGrid API keys typically start with "SG."');
}
// Check for minimum length (SendGrid keys are typically 69 characters)
if (typeof sendGridConfig.apiKey === 'string' && sendGridConfig.apiKey.length < 20) {
throw new ConfigurationError('SendGrid API key appears to be too short. Please verify your API key.');
}
break;
case EmailProviderType.SES:
// AWS SES Configuration Validation
const sesConfig = config.config as SesConfig;
if (!sesConfig.region) {
throw new ConfigurationError('AWS SES region is required');
}
// Validate that at least one credential method is provided
const hasCredentials = sesConfig.credentials ||
(sesConfig.accessKeyId && sesConfig.secretAccessKey) ||
process.env.AWS_ACCESS_KEY_ID;
if (!hasCredentials) {
throw new ConfigurationError('AWS SES credentials are required (accessKeyId/secretAccessKey, credentials object, or AWS environment variables)');
}
break;
default:
throw new ConfigurationError(`Unsupported provider type: ${config.type}`);
}
}
}