import * as plugins from '../../plugins.js'; import type { OpsServer } from '../classes.opsserver.js'; import * as interfaces from '../../../ts_interfaces/index.js'; export class EmailOpsHandler { public typedrouter = new plugins.typedrequest.TypedRouter(); constructor(private opsServerRef: OpsServer) { // Add this handler's router to the parent this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter); this.registerHandlers(); } private registerHandlers(): void { // Get All Emails Handler this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getAllEmails', async (dataArg) => { const emails = this.getAllQueueEmails(); return { emails }; } ) ); // Get Email Detail Handler this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getEmailDetail', async (dataArg) => { const email = this.getEmailDetail(dataArg.emailId); return { email }; } ) ); // Resend Failed Email Handler this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'resendEmail', async (dataArg) => { const emailServer = this.opsServerRef.dcRouterRef.emailServer; if (!emailServer?.deliveryQueue) { return { success: false, error: 'Email server not available' }; } const queue = emailServer.deliveryQueue; const item = queue.getItem(dataArg.emailId); if (!item) { return { success: false, error: 'Email not found in queue' }; } if (item.status !== 'failed') { return { success: false, error: `Email is not in failed state (current: ${item.status})` }; } try { const newQueueId = await queue.enqueue( item.processingResult, item.processingMode, item.route ); await queue.removeItem(dataArg.emailId); return { success: true, newQueueId }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Failed to resend email' }; } } ) ); } /** * Get all queue items mapped to catalog IEmail format */ private getAllQueueEmails(): interfaces.requests.IEmail[] { const emailServer = this.opsServerRef.dcRouterRef.emailServer; if (!emailServer?.deliveryQueue) { return []; } const queue = emailServer.deliveryQueue; const queueMap = (queue as any).queue as Map; if (!queueMap) { return []; } const emails: interfaces.requests.IEmail[] = []; for (const [id, item] of queueMap.entries()) { emails.push(this.mapQueueItemToEmail(item)); } // Sort by createdAt descending (newest first) emails.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); return emails; } /** * Get a single email detail by ID */ private getEmailDetail(emailId: string): interfaces.requests.IEmailDetail | null { const emailServer = this.opsServerRef.dcRouterRef.emailServer; if (!emailServer?.deliveryQueue) { return null; } const queue = emailServer.deliveryQueue; const item = queue.getItem(emailId); if (!item) { return null; } return this.mapQueueItemToEmailDetail(item); } /** * Map a queue item to catalog IEmail format */ private mapQueueItemToEmail(item: any): interfaces.requests.IEmail { const processingResult = item.processingResult; let from = ''; let to = ''; let subject = ''; let messageId = ''; let size = '0 B'; if (processingResult) { if (processingResult.email) { from = processingResult.email.from || ''; to = (processingResult.email.to || [])[0] || ''; subject = processingResult.email.subject || ''; } else if (processingResult.from) { from = processingResult.from; to = (processingResult.to || [])[0] || ''; subject = processingResult.subject || ''; } // Try to get messageId if (typeof processingResult.getMessageId === 'function') { try { messageId = processingResult.getMessageId() || ''; } catch { messageId = ''; } } // Compute approximate size const textLen = processingResult.text?.length || 0; const htmlLen = processingResult.html?.length || 0; let attachSize = 0; if (typeof processingResult.getAttachmentsSize === 'function') { try { attachSize = processingResult.getAttachmentsSize() || 0; } catch { attachSize = 0; } } size = this.formatSize(textLen + htmlLen + attachSize); } // Map queue status to catalog TEmailStatus const status = this.mapStatus(item.status); const createdAt = item.createdAt instanceof Date ? item.createdAt.getTime() : item.createdAt; return { id: item.id, direction: 'outbound' as interfaces.requests.TEmailDirection, status, from, to, subject, timestamp: new Date(createdAt).toISOString(), messageId, size, }; } /** * Map a queue item to catalog IEmailDetail format */ private mapQueueItemToEmailDetail(item: any): interfaces.requests.IEmailDetail { const base = this.mapQueueItemToEmail(item); const processingResult = item.processingResult; let toList: string[] = []; let cc: string[] = []; let headers: Record = {}; let body = ''; if (processingResult) { if (processingResult.email) { toList = processingResult.email.to || []; cc = processingResult.email.cc || []; } else { toList = processingResult.to || []; cc = processingResult.cc || []; } headers = processingResult.headers || {}; body = processingResult.html || processingResult.text || ''; } return { ...base, toList, cc, smtpLog: [], connectionInfo: { sourceIp: '', sourceHostname: '', destinationIp: '', destinationPort: 0, tlsVersion: '', tlsCipher: '', authenticated: false, authMethod: '', authUser: '', }, authenticationResults: { spf: 'none', spfDomain: '', dkim: 'none', dkimDomain: '', dmarc: 'none', dmarcPolicy: '', }, rejectionReason: item.status === 'failed' ? item.lastError : undefined, bounceMessage: item.status === 'failed' ? item.lastError : undefined, headers, body, }; } /** * Map queue status to catalog TEmailStatus */ private mapStatus(queueStatus: string): interfaces.requests.TEmailStatus { switch (queueStatus) { case 'pending': case 'processing': return 'pending'; case 'delivered': return 'delivered'; case 'failed': return 'bounced'; case 'deferred': return 'deferred'; default: return 'pending'; } } /** * Format byte size to human-readable string */ private formatSize(bytes: number): string { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; } }