import { PlatformError, NetworkError, AuthenticationError, OperationError, ConfigurationError } from './base.errors.js'; import type { IErrorContext } from './base.errors.js'; import { MTA_CONNECTION_ERROR, MTA_AUTHENTICATION_ERROR, MTA_DELIVERY_ERROR, MTA_CONFIGURATION_ERROR, MTA_DNS_ERROR, MTA_TIMEOUT_ERROR, MTA_PROTOCOL_ERROR } from './error.codes.js'; /** * Base class for MTA connection errors */ export class MtaConnectionError extends NetworkError { /** * Creates a new MTA connection error * * @param message Error message * @param context Additional context */ constructor( message: string, context: IErrorContext = {} ) { super(message, MTA_CONNECTION_ERROR, context); } /** * Creates an instance for a DNS resolution error * * @param hostname Hostname that failed to resolve * @param originalError Original error * @param context Additional context */ public static dnsError( hostname: string, originalError?: Error, context: IErrorContext = {} ): MtaConnectionError { const errorMsg = originalError ? `: ${originalError.message}` : ''; return new MtaConnectionError( `Failed to resolve DNS for ${hostname}${errorMsg}`, { ...context, data: { ...context.data, hostname, originalError: originalError ? { message: originalError.message, stack: originalError.stack } : undefined }, userMessage: `Could not connect to mail server for ${hostname}.` } ); } /** * Creates an instance for a connection timeout * * @param hostname Hostname that timed out * @param port Port number * @param timeout Timeout in milliseconds * @param context Additional context */ public static timeout( hostname: string, port: number, timeout: number, context: IErrorContext = {} ): MtaConnectionError { return new MtaConnectionError( `Connection to ${hostname}:${port} timed out after ${timeout}ms`, { ...context, data: { ...context.data, hostname, port, timeout }, userMessage: `Connection to mail server timed out.` } ); } /** * Creates an instance for a connection refused error * * @param hostname Hostname that refused connection * @param port Port number * @param context Additional context */ public static refused( hostname: string, port: number, context: IErrorContext = {} ): MtaConnectionError { return new MtaConnectionError( `Connection to ${hostname}:${port} refused`, { ...context, data: { ...context.data, hostname, port }, userMessage: `Connection to mail server was refused.` } ); } } /** * Error class for MTA authentication errors */ export class MtaAuthenticationError extends AuthenticationError { /** * Creates a new MTA authentication error * * @param message Error message * @param context Additional context */ constructor( message: string, context: IErrorContext = {} ) { super(message, MTA_AUTHENTICATION_ERROR, context); } /** * Creates an instance for invalid credentials * * @param hostname Hostname where authentication failed * @param username Username that failed authentication * @param context Additional context */ public static invalidCredentials( hostname: string, username: string, context: IErrorContext = {} ): MtaAuthenticationError { return new MtaAuthenticationError( `Authentication failed for user ${username} at ${hostname}`, { ...context, data: { ...context.data, hostname, username }, userMessage: `Authentication to mail server failed.` } ); } /** * Creates an instance for unsupported authentication method * * @param hostname Hostname * @param method Authentication method that is not supported * @param supportedMethods List of supported authentication methods * @param context Additional context */ public static unsupportedMethod( hostname: string, method: string, supportedMethods: string[] = [], context: IErrorContext = {} ): MtaAuthenticationError { return new MtaAuthenticationError( `Authentication method ${method} not supported by ${hostname}${supportedMethods.length > 0 ? `. Supported methods: ${supportedMethods.join(', ')}` : ''}`, { ...context, data: { ...context.data, hostname, method, supportedMethods }, userMessage: `The mail server doesn't support the required authentication method.` } ); } } /** * Error class for MTA delivery errors */ export class MtaDeliveryError extends OperationError { /** * Creates a new MTA delivery error * * @param message Error message * @param context Additional context */ constructor( message: string, context: IErrorContext = {} ) { super(message, MTA_DELIVERY_ERROR, context); } /** * Creates an instance for a permanent delivery failure * * @param message Error message * @param recipientAddress Recipient email address * @param statusCode SMTP status code * @param smtpResponse Full SMTP response * @param context Additional context */ public static permanent( message: string, recipientAddress: string, statusCode?: string, smtpResponse?: string, context: IErrorContext = {} ): MtaDeliveryError { const statusCodeStr = statusCode ? ` (${statusCode})` : ''; return new MtaDeliveryError( `Permanent delivery failure to ${recipientAddress}${statusCodeStr}: ${message}`, { ...context, data: { ...context.data, recipientAddress, statusCode, smtpResponse, permanent: true }, userMessage: `The email could not be delivered to ${recipientAddress}.` } ); } /** * Creates an instance for a temporary delivery failure * * @param message Error message * @param recipientAddress Recipient email address * @param statusCode SMTP status code * @param smtpResponse Full SMTP response * @param maxRetries Maximum number of retries * @param currentRetry Current retry count * @param retryDelay Delay between retries in ms * @param context Additional context */ public static temporary( message: string, recipientAddress: string, statusCode?: string, smtpResponse?: string, maxRetries: number = 3, currentRetry: number = 0, retryDelay: number = 60000, context: IErrorContext = {} ): MtaDeliveryError { const statusCodeStr = statusCode ? ` (${statusCode})` : ''; const error = new MtaDeliveryError( `Temporary delivery failure to ${recipientAddress}${statusCodeStr}: ${message}`, { ...context, data: { ...context.data, recipientAddress, statusCode, smtpResponse, permanent: false }, userMessage: `The email delivery to ${recipientAddress} failed temporarily. It will be retried.` } ); return error.withRetry(maxRetries, currentRetry, retryDelay) as MtaDeliveryError; } /** * Check if this is a permanent delivery failure */ public isPermanent(): boolean { return !!this.context.data?.permanent; } /** * Get the recipient address associated with this delivery error */ public getRecipientAddress(): string | undefined { return this.context.data?.recipientAddress; } /** * Get the SMTP status code associated with this delivery error */ public getStatusCode(): string | undefined { return this.context.data?.statusCode; } } /** * Error class for MTA configuration errors */ export class MtaConfigurationError extends ConfigurationError { /** * Creates a new MTA configuration error * * @param message Error message * @param context Additional context */ constructor( message: string, context: IErrorContext = {} ) { super(message, MTA_CONFIGURATION_ERROR, context); } /** * Creates an instance for a missing configuration value * * @param propertyPath Path to the missing property * @param context Additional context */ public static missingConfig( propertyPath: string, context: IErrorContext = {} ): MtaConfigurationError { return new MtaConfigurationError( `Missing required configuration: ${propertyPath}`, { ...context, data: { ...context.data, propertyPath }, userMessage: `The mail server is missing required configuration.` } ); } /** * Creates an instance for an invalid configuration value * * @param propertyPath Path to the invalid property * @param value Current value * @param expectedType Expected type or format * @param context Additional context */ public static invalidConfig( propertyPath: string, value: any, expectedType: string, context: IErrorContext = {} ): MtaConfigurationError { return new MtaConfigurationError( `Invalid configuration value for ${propertyPath}: got ${value} (${typeof value}), expected ${expectedType}`, { ...context, data: { ...context.data, propertyPath, value, expectedType }, userMessage: `The mail server has an invalid configuration value.` } ); } } /** * Error class for MTA DNS errors */ export class MtaDnsError extends NetworkError { /** * Creates a new MTA DNS error * * @param message Error message * @param context Additional context */ constructor( message: string, context: IErrorContext = {} ) { super(message, MTA_DNS_ERROR, context); } /** * Creates an instance for an MX record lookup failure * * @param domain Domain that failed MX lookup * @param originalError Original error * @param context Additional context */ public static mxLookupFailed( domain: string, originalError?: Error, context: IErrorContext = {} ): MtaDnsError { const errorMsg = originalError ? `: ${originalError.message}` : ''; return new MtaDnsError( `Failed to lookup MX records for ${domain}${errorMsg}`, { ...context, data: { ...context.data, domain, recordType: 'MX', originalError: originalError ? { message: originalError.message, stack: originalError.stack } : undefined }, userMessage: `Could not find mail servers for ${domain}.` } ); } /** * Creates an instance for a TXT record lookup failure * * @param domain Domain that failed TXT lookup * @param recordPrefix Optional record prefix (e.g., 'spf', 'dkim', 'dmarc') * @param originalError Original error * @param context Additional context */ public static txtLookupFailed( domain: string, recordPrefix?: string, originalError?: Error, context: IErrorContext = {} ): MtaDnsError { const recordType = recordPrefix ? `${recordPrefix} TXT` : 'TXT'; const errorMsg = originalError ? `: ${originalError.message}` : ''; return new MtaDnsError( `Failed to lookup ${recordType} records for ${domain}${errorMsg}`, { ...context, data: { ...context.data, domain, recordType, recordPrefix, originalError: originalError ? { message: originalError.message, stack: originalError.stack } : undefined }, userMessage: `Could not verify ${recordPrefix || ''} records for ${domain}.` } ); } } /** * Error class for MTA timeout errors */ export class MtaTimeoutError extends NetworkError { /** * Creates a new MTA timeout error * * @param message Error message * @param context Additional context */ constructor( message: string, context: IErrorContext = {} ) { super(message, MTA_TIMEOUT_ERROR, context); } /** * Creates an instance for an SMTP command timeout * * @param command SMTP command that timed out * @param server Server hostname * @param timeout Timeout in milliseconds * @param context Additional context */ public static commandTimeout( command: string, server: string, timeout: number, context: IErrorContext = {} ): MtaTimeoutError { return new MtaTimeoutError( `SMTP command ${command} to ${server} timed out after ${timeout}ms`, { ...context, data: { ...context.data, command, server, timeout }, userMessage: `The mail server took too long to respond.` } ); } /** * Creates an instance for an overall transaction timeout * * @param server Server hostname * @param timeout Timeout in milliseconds * @param context Additional context */ public static transactionTimeout( server: string, timeout: number, context: IErrorContext = {} ): MtaTimeoutError { return new MtaTimeoutError( `SMTP transaction with ${server} timed out after ${timeout}ms`, { ...context, data: { ...context.data, server, timeout }, userMessage: `The mail server transaction took too long to complete.` } ); } } /** * Error class for MTA protocol errors */ export class MtaProtocolError extends OperationError { /** * Creates a new MTA protocol error * * @param message Error message * @param context Additional context */ constructor( message: string, context: IErrorContext = {} ) { super(message, MTA_PROTOCOL_ERROR, context); } /** * Creates an instance for an unexpected server response * * @param command SMTP command that received unexpected response * @param response Unexpected response * @param expected Expected response pattern * @param server Server hostname * @param context Additional context */ public static unexpectedResponse( command: string, response: string, expected: string, server: string, context: IErrorContext = {} ): MtaProtocolError { return new MtaProtocolError( `Unexpected SMTP response from ${server} for command ${command}: got "${response}", expected "${expected}"`, { ...context, data: { ...context.data, command, response, expected, server }, userMessage: `Received an unexpected response from the mail server.` } ); } /** * Creates an instance for a syntax error * * @param details Error details * @param server Server hostname * @param context Additional context */ public static syntaxError( details: string, server: string, context: IErrorContext = {} ): MtaProtocolError { return new MtaProtocolError( `SMTP syntax error in communication with ${server}: ${details}`, { ...context, data: { ...context.data, details, server }, userMessage: `There was a protocol error communicating with the mail server.` } ); } }