@454creative/easy-email
Version:
A framework-agnostic email service library for Node.js with observability and monitoring
129 lines (111 loc) • 4.39 kB
text/typescript
import {
SmtpConfig,
SendGridConfig,
SesConfig,
EmailProviderConfig,
EmailProviderType,
} from '../interfaces/email-options.interface';
import { ConfigurationError, ValidationError } from '../errors';
export class ConfigValidator {
static validateSmtpConfig(config: SmtpConfig): void {
if (!config.host) {
throw new ConfigurationError('SMTP host is required');
}
if (!config.port) {
throw new ConfigurationError('SMTP port is required');
}
if (config.auth) {
if (!config.auth.user) {
throw new ConfigurationError('SMTP username is required when auth is provided');
}
if (!config.auth.pass) {
throw new ConfigurationError('SMTP password is required when auth is provided');
}
}
}
static validateSendGridConfig(config: SendGridConfig): void {
if (!config.apiKey) {
throw new ConfigurationError('SendGrid API key is required');
}
// Check for basic API key format (SendGrid keys typically start with "SG.")
if (typeof config.apiKey === 'string' && !config.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 config.apiKey === 'string' && config.apiKey.length < 20) {
throw new ConfigurationError('SendGrid API key appears to be too short. Please verify your API key.');
}
}
static validateSesConfig(config: SesConfig): void {
if (!config.region) {
throw new ConfigurationError('SES region is required');
}
// Validate that at least one credential method is provided
const hasCredentials = config.credentials ||
(config.accessKeyId && config.secretAccessKey) ||
process.env.AWS_ACCESS_KEY_ID;
if (!hasCredentials) {
throw new ConfigurationError('SES credentials are required (accessKeyId/secretAccessKey, credentials object, or AWS environment variables)');
}
// Validate region format (basic check)
if (!/^[a-z0-9-]+$/.test(config.region)) {
throw new ConfigurationError('Invalid SES region format');
}
// Validate optional parameters
if (config.maxRetries !== undefined && config.maxRetries < 0) {
throw new ConfigurationError('SES maxRetries must be a non-negative number');
}
if (config.httpOptions) {
if (config.httpOptions.timeout !== undefined && config.httpOptions.timeout < 0) {
throw new ConfigurationError('SES httpOptions timeout must be a non-negative number');
}
if (config.httpOptions.connectTimeout !== undefined && config.httpOptions.connectTimeout < 0) {
throw new ConfigurationError('SES httpOptions connectTimeout must be a non-negative number');
}
}
}
static validateProviderConfig(config: EmailProviderConfig): void {
if (!config.type) {
throw new ConfigurationError('Provider type is required');
}
switch (config.type) {
case EmailProviderType.SMTP:
this.validateSmtpConfig(config.config as SmtpConfig);
break;
case EmailProviderType.SENDGRID:
this.validateSendGridConfig(config.config as SendGridConfig);
break;
case EmailProviderType.SES:
this.validateSesConfig(config.config as SesConfig);
break;
default:
throw new ConfigurationError(`Unsupported provider type: ${config.type}`);
}
}
static validateEmail(email: string): void {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
throw new ValidationError(`Invalid email format: ${email}`);
}
}
static validatePort(port: number): void {
if (port < 1 || port > 65535) {
throw new ConfigurationError(`Invalid port number: ${port}`);
}
}
static validateTimeout(timeout: number): void {
if (timeout < 0) {
throw new ConfigurationError('Timeout must be a positive number');
}
}
static validateRetryAttempts(attempts: number): void {
if (attempts < 0) {
throw new ConfigurationError('Retry attempts must be a positive number');
}
}
static validateRetryDelay(delay: number): void {
if (delay < 0) {
throw new ConfigurationError('Retry delay must be a positive number');
}
}
}