platformservice/ts/errors/mta.errors.ts

611 lines
15 KiB
TypeScript
Raw Normal View History

2025-05-08 12:46:10 +00:00
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.`
}
);
}
}