2025-05-08 01:13:54 +00:00
|
|
|
import * as plugins from '../../plugins.js';
|
|
|
|
import * as paths from '../../paths.js';
|
|
|
|
import { MtaConnector } from '../delivery/classes.connector.mta.js';
|
|
|
|
import { RuleManager } from '../core/classes.rulemanager.js';
|
2025-03-15 16:21:37 +00:00
|
|
|
import { ApiManager } from './classes.apimanager.js';
|
2025-05-08 01:13:54 +00:00
|
|
|
import { TemplateManager } from '../core/classes.templatemanager.js';
|
|
|
|
import { EmailValidator } from '../core/classes.emailvalidator.js';
|
|
|
|
import { BounceManager } from '../core/classes.bouncemanager.js';
|
|
|
|
import { logger } from '../../logger.js';
|
|
|
|
import type { SzPlatformService } from '../../platformservice.js';
|
2024-02-15 20:30:38 +01:00
|
|
|
|
2025-03-15 16:04:03 +00:00
|
|
|
// Import MTA service
|
2025-05-08 12:46:10 +00:00
|
|
|
import { MtaService } from '../delivery/classes.mta.js';
|
|
|
|
|
|
|
|
// Import configuration interfaces
|
|
|
|
import type { IEmailConfig } from '../../config/email.config.js';
|
|
|
|
import { ConfigValidator, emailConfigSchema } from '../../config/index.js';
|
2024-02-16 20:42:26 +01:00
|
|
|
|
2025-05-08 10:39:43 +00:00
|
|
|
/**
|
|
|
|
* Options for sending an email
|
|
|
|
* @see ISendEmailOptions in MtaConnector
|
|
|
|
*/
|
|
|
|
export type ISendEmailOptions = import('../delivery/classes.connector.mta.js').ISendEmailOptions;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Template context data for email templates
|
|
|
|
* @see ITemplateContext in TemplateManager
|
|
|
|
*/
|
|
|
|
export type ITemplateContext = import('../core/classes.templatemanager.js').ITemplateContext;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Validation options for email addresses
|
|
|
|
* Compatible with EmailValidator.validate options
|
|
|
|
*/
|
|
|
|
export interface IValidateEmailOptions {
|
|
|
|
/** Check MX records for the domain */
|
|
|
|
checkMx?: boolean;
|
|
|
|
/** Check if the domain is disposable (temporary email) */
|
|
|
|
checkDisposable?: boolean;
|
|
|
|
/** Check if the email is a role account (e.g., info@, support@) */
|
|
|
|
checkRole?: boolean;
|
|
|
|
/** Only check syntax without DNS lookups */
|
|
|
|
checkSyntaxOnly?: boolean;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Result of email validation
|
|
|
|
* @see IEmailValidationResult from EmailValidator
|
|
|
|
*/
|
|
|
|
export type IValidationResult = import('../core/classes.emailvalidator.js').IEmailValidationResult;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Email service statistics
|
|
|
|
*/
|
|
|
|
export interface IEmailServiceStats {
|
|
|
|
/** Active email providers */
|
|
|
|
activeProviders: string[];
|
|
|
|
/** MTA statistics, if MTA is active */
|
|
|
|
mta?: {
|
|
|
|
/** Service start time */
|
|
|
|
startTime: Date;
|
|
|
|
/** Total emails received */
|
|
|
|
emailsReceived: number;
|
|
|
|
/** Total emails sent */
|
|
|
|
emailsSent: number;
|
|
|
|
/** Total emails that failed to send */
|
|
|
|
emailsFailed: number;
|
|
|
|
/** Active SMTP connections */
|
|
|
|
activeConnections: number;
|
|
|
|
/** Current email queue size */
|
|
|
|
queueSize: number;
|
|
|
|
/** Certificate information */
|
|
|
|
certificateInfo?: {
|
|
|
|
/** Domain for the certificate */
|
|
|
|
domain: string;
|
|
|
|
/** Certificate expiration date */
|
|
|
|
expiresAt: Date;
|
|
|
|
/** Days until certificate expires */
|
|
|
|
daysUntilExpiry: number;
|
|
|
|
};
|
|
|
|
/** IP warmup information */
|
|
|
|
warmupInfo?: {
|
|
|
|
/** Whether IP warmup is enabled */
|
|
|
|
enabled: boolean;
|
|
|
|
/** Number of active IPs */
|
|
|
|
activeIPs: number;
|
|
|
|
/** Number of IPs in warmup phase */
|
|
|
|
inWarmupPhase: number;
|
|
|
|
/** Number of IPs that completed warmup */
|
|
|
|
completedWarmup: number;
|
|
|
|
};
|
|
|
|
/** Reputation monitoring information */
|
|
|
|
reputationInfo?: {
|
|
|
|
/** Whether reputation monitoring is enabled */
|
|
|
|
enabled: boolean;
|
|
|
|
/** Number of domains being monitored */
|
|
|
|
monitoredDomains: number;
|
|
|
|
/** Average reputation score across domains */
|
|
|
|
averageScore: number;
|
|
|
|
/** Number of domains with reputation issues */
|
|
|
|
domainsWithIssues: number;
|
|
|
|
};
|
|
|
|
/** Rate limiting information */
|
|
|
|
rateLimiting?: {
|
|
|
|
/** Global rate limit statistics */
|
|
|
|
global: {
|
|
|
|
/** Current available tokens */
|
|
|
|
availableTokens: number;
|
|
|
|
/** Maximum tokens per period */
|
|
|
|
maxTokens: number;
|
|
|
|
/** Current consumption rate */
|
|
|
|
consumptionRate: number;
|
|
|
|
/** Number of rate limiting events */
|
|
|
|
rateLimitEvents: number;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2025-03-15 16:04:03 +00:00
|
|
|
/**
|
2025-05-08 01:37:38 +00:00
|
|
|
* Email service with MTA support
|
2025-03-15 16:04:03 +00:00
|
|
|
*/
|
2024-02-16 13:28:40 +01:00
|
|
|
export class EmailService {
|
2024-02-15 20:30:38 +01:00
|
|
|
public platformServiceRef: SzPlatformService;
|
|
|
|
|
|
|
|
// typedrouter
|
2024-02-16 13:28:40 +01:00
|
|
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
2024-02-15 20:30:38 +01:00
|
|
|
|
|
|
|
// connectors
|
2025-03-15 16:04:03 +00:00
|
|
|
public mtaConnector: MtaConnector;
|
2024-02-15 20:30:38 +01:00
|
|
|
public qenv = new plugins.qenv.Qenv('./', '.nogit/');
|
|
|
|
|
2025-03-15 16:04:03 +00:00
|
|
|
// MTA service
|
|
|
|
public mtaService: MtaService;
|
|
|
|
|
|
|
|
// services
|
|
|
|
public apiManager: ApiManager;
|
2024-02-15 20:30:38 +01:00
|
|
|
public ruleManager: RuleManager;
|
2025-05-07 17:41:04 +00:00
|
|
|
public templateManager: TemplateManager;
|
|
|
|
public emailValidator: EmailValidator;
|
2025-05-07 20:20:17 +00:00
|
|
|
public bounceManager: BounceManager;
|
2024-02-15 20:30:38 +01:00
|
|
|
|
2025-03-15 16:04:03 +00:00
|
|
|
// configuration
|
2025-05-08 12:46:10 +00:00
|
|
|
private config: IEmailConfig;
|
2025-03-15 16:04:03 +00:00
|
|
|
|
2025-05-08 12:46:10 +00:00
|
|
|
constructor(platformServiceRefArg: SzPlatformService, options: IEmailConfig = {}) {
|
2024-02-15 20:30:38 +01:00
|
|
|
this.platformServiceRef = platformServiceRefArg;
|
2024-02-16 13:28:40 +01:00
|
|
|
this.platformServiceRef.typedrouter.addTypedRouter(this.typedrouter);
|
2025-03-15 16:04:03 +00:00
|
|
|
|
2025-05-08 12:46:10 +00:00
|
|
|
// Validate and apply defaults to configuration
|
|
|
|
const validationResult = ConfigValidator.validate(options, emailConfigSchema);
|
|
|
|
|
|
|
|
if (!validationResult.valid) {
|
|
|
|
logger.warn(`Email service configuration has validation errors: ${validationResult.errors.join(', ')}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set configuration with defaults
|
|
|
|
this.config = validationResult.config;
|
2025-03-15 16:04:03 +00:00
|
|
|
|
2025-05-07 17:41:04 +00:00
|
|
|
// Initialize validator
|
|
|
|
this.emailValidator = new EmailValidator();
|
2025-05-07 20:20:17 +00:00
|
|
|
|
|
|
|
// Initialize bounce manager
|
|
|
|
this.bounceManager = new BounceManager();
|
2025-05-07 17:41:04 +00:00
|
|
|
|
|
|
|
// Initialize template manager
|
|
|
|
this.templateManager = new TemplateManager(this.config.templateConfig);
|
|
|
|
|
2025-03-15 16:04:03 +00:00
|
|
|
if (this.config.useMta) {
|
|
|
|
// Initialize MTA service
|
|
|
|
this.mtaService = new MtaService(platformServiceRefArg, this.config.mtaConfig);
|
|
|
|
// Initialize MTA connector
|
|
|
|
this.mtaConnector = new MtaConnector(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize API manager and rule manager
|
|
|
|
this.apiManager = new ApiManager(this);
|
2024-02-15 20:30:38 +01:00
|
|
|
this.ruleManager = new RuleManager(this);
|
2025-03-15 16:04:03 +00:00
|
|
|
|
|
|
|
// Set up MTA SMTP server webhook if using MTA
|
|
|
|
if (this.config.useMta) {
|
|
|
|
// The MTA SMTP server will handle incoming emails directly
|
|
|
|
// through its SMTP protocol. No additional webhook needed.
|
|
|
|
}
|
2024-02-15 20:30:38 +01:00
|
|
|
}
|
|
|
|
|
2025-03-15 16:04:03 +00:00
|
|
|
/**
|
|
|
|
* Start the email service
|
|
|
|
*/
|
2024-02-15 20:30:38 +01:00
|
|
|
public async start() {
|
2025-03-15 16:04:03 +00:00
|
|
|
// Initialize rule manager
|
2024-02-15 20:30:38 +01:00
|
|
|
await this.ruleManager.init();
|
2025-03-15 16:04:03 +00:00
|
|
|
|
2025-05-07 17:41:04 +00:00
|
|
|
// Load email templates if configured
|
|
|
|
if (this.config.loadTemplatesFromDir) {
|
|
|
|
try {
|
|
|
|
await this.templateManager.loadTemplatesFromDirectory(paths.emailTemplatesDir);
|
|
|
|
} catch (error) {
|
|
|
|
logger.log('error', `Failed to load email templates: ${error.message}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-15 16:04:03 +00:00
|
|
|
// Start MTA service if enabled
|
|
|
|
if (this.config.useMta && this.mtaService) {
|
|
|
|
await this.mtaService.start();
|
|
|
|
logger.log('success', 'Started MTA service');
|
|
|
|
}
|
|
|
|
|
2024-02-15 20:30:38 +01:00
|
|
|
logger.log('success', `Started email service`);
|
|
|
|
}
|
|
|
|
|
2025-03-15 16:04:03 +00:00
|
|
|
/**
|
|
|
|
* Stop the email service
|
|
|
|
*/
|
2024-02-15 20:30:38 +01:00
|
|
|
public async stop() {
|
2025-03-15 16:04:03 +00:00
|
|
|
// Stop MTA service if it's running
|
|
|
|
if (this.config.useMta && this.mtaService) {
|
|
|
|
await this.mtaService.stop();
|
|
|
|
logger.log('info', 'Stopped MTA service');
|
|
|
|
}
|
|
|
|
|
|
|
|
logger.log('info', 'Stopped email service');
|
2024-02-15 20:30:38 +01:00
|
|
|
}
|
|
|
|
|
2025-03-15 16:04:03 +00:00
|
|
|
/**
|
2025-05-08 01:37:38 +00:00
|
|
|
* Send an email using the MTA
|
2025-03-15 16:04:03 +00:00
|
|
|
* @param email The email to send
|
|
|
|
* @param to Recipient(s)
|
|
|
|
* @param options Additional options
|
|
|
|
*/
|
|
|
|
public async sendEmail(
|
2025-05-07 17:41:04 +00:00
|
|
|
email: plugins.smartmail.Smartmail<any>,
|
2025-03-15 16:04:03 +00:00
|
|
|
to: string | string[],
|
2025-05-08 10:39:43 +00:00
|
|
|
options: ISendEmailOptions = {}
|
2025-03-15 16:04:03 +00:00
|
|
|
): Promise<string> {
|
|
|
|
// Determine which connector to use
|
|
|
|
if (this.config.useMta && this.mtaConnector) {
|
|
|
|
return this.mtaConnector.sendEmail(email, to, options);
|
|
|
|
} else {
|
2025-05-08 01:37:38 +00:00
|
|
|
throw new Error('MTA not configured');
|
2025-03-15 16:04:03 +00:00
|
|
|
}
|
|
|
|
}
|
2025-05-07 17:41:04 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Send an email using a template
|
|
|
|
* @param templateId The template ID
|
|
|
|
* @param to Recipient email(s)
|
|
|
|
* @param context The template context data
|
|
|
|
* @param options Additional options
|
|
|
|
*/
|
|
|
|
public async sendTemplateEmail(
|
|
|
|
templateId: string,
|
|
|
|
to: string | string[],
|
2025-05-08 10:39:43 +00:00
|
|
|
context: ITemplateContext = {},
|
|
|
|
options: ISendEmailOptions = {}
|
2025-05-07 17:41:04 +00:00
|
|
|
): Promise<string> {
|
|
|
|
try {
|
|
|
|
// Get email from template
|
|
|
|
const smartmail = await this.templateManager.prepareEmail(templateId, context);
|
|
|
|
|
|
|
|
// Send the email
|
|
|
|
return this.sendEmail(smartmail, to, options);
|
|
|
|
} catch (error) {
|
|
|
|
logger.log('error', `Failed to send template email: ${error.message}`, {
|
|
|
|
templateId,
|
|
|
|
to,
|
|
|
|
error: error.message
|
|
|
|
});
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Validate an email address
|
|
|
|
* @param email The email address to validate
|
|
|
|
* @param options Validation options
|
|
|
|
* @returns Validation result
|
|
|
|
*/
|
|
|
|
public async validateEmail(
|
|
|
|
email: string,
|
2025-05-08 10:39:43 +00:00
|
|
|
options: IValidateEmailOptions = {}
|
|
|
|
): Promise<IValidationResult> {
|
2025-05-07 17:41:04 +00:00
|
|
|
return this.emailValidator.validate(email, options);
|
|
|
|
}
|
2025-03-15 16:04:03 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get email service statistics
|
2025-05-08 10:39:43 +00:00
|
|
|
* @returns Service statistics in the format expected by the API
|
2025-03-15 16:04:03 +00:00
|
|
|
*/
|
2025-05-08 10:39:43 +00:00
|
|
|
public getStats(): plugins.servezoneInterfaces.platformservice.mta.IReq_GetEMailStats['response'] {
|
|
|
|
// First generate detailed internal stats
|
|
|
|
const detailedStats: IEmailServiceStats = {
|
2025-03-15 16:04:03 +00:00
|
|
|
activeProviders: []
|
|
|
|
};
|
|
|
|
|
|
|
|
if (this.config.useMta) {
|
2025-05-08 10:39:43 +00:00
|
|
|
detailedStats.activeProviders.push('mta');
|
|
|
|
detailedStats.mta = this.mtaService.getStats();
|
2025-03-15 16:04:03 +00:00
|
|
|
}
|
|
|
|
|
2025-05-08 10:39:43 +00:00
|
|
|
// Convert detailed stats to the format expected by the API
|
|
|
|
const apiStats: plugins.servezoneInterfaces.platformservice.mta.IReq_GetEMailStats['response'] = {
|
|
|
|
totalEmailsSent: detailedStats.mta?.emailsSent || 0,
|
|
|
|
totalEmailsDelivered: detailedStats.mta?.emailsSent || 0, // Default to emails sent if we don't track delivery separately
|
|
|
|
totalEmailsBounced: detailedStats.mta?.emailsFailed || 0,
|
|
|
|
averageDeliveryTimeMs: 0, // We don't track this yet
|
|
|
|
lastUpdated: new Date().toISOString()
|
|
|
|
};
|
|
|
|
|
|
|
|
return apiStats;
|
2025-03-15 16:04:03 +00:00
|
|
|
}
|
|
|
|
}
|