import { Injectable, Logger } from "@nestjs/common"; import { firstValueFrom } from "rxjs"; import { MailServerService } from "../../mail-server/services/mail-server.service"; import { EmailGateway } from "../email.gateway"; import { MailboxResolverService } from "./mailbox-resolver.service"; import { EmailNotificationPayload } from "../interfaces/email-events.interface"; @Injectable() export class EmailNotificationService { private readonly logger = new Logger(EmailNotificationService.name); constructor( private readonly emailGateway: EmailGateway, private readonly mailServerService: MailServerService, private readonly mailboxResolverService: MailboxResolverService, ) {} /** * Handle new incoming message and emit WebSocket notification * This method can be called by webhook handlers, polling services, etc. */ async handleNewMessage(userId: string, messageId: number, mailboxId?: string) { try { this.logger.log(`Processing new message notification for user ${userId}, message ${messageId}`); // Get the mailbox ID if not provided const targetMailboxId = mailboxId || (await this.mailboxResolverService.getInboxMailboxId(userId)); // Fetch the message details const message = await firstValueFrom(this.mailServerService.messages.getMessage(userId, targetMailboxId, messageId)); // Determine mailbox name const mailboxName = await this.getMailboxName(userId, targetMailboxId); // Create notification payload const notificationPayload: EmailNotificationPayload = { messageId, userId, from: { name: message.from?.name || message.from?.address?.split("@")[0] || "Unknown", address: message.from?.address || "unknown@example.com", }, to: message.to?.map((recipient) => ({ name: recipient.name || recipient.address?.split("@")[0] || "Unknown", address: recipient.address || "unknown@example.com", })) || [], subject: message.subject || "No Subject", preview: this.extractPreview( typeof message.text === "string" ? message.text : Array.isArray(message.html) ? message.html.join("") : message.html || "", ), hasAttachments: Array.isArray(message.attachments) && message.attachments.length > 0, received: message.idate || new Date().toISOString(), mailboxId: targetMailboxId, mailboxName, isRead: message.seen || false, isFlagged: message.flagged || false, size: message.size || 0, }; // Emit WebSocket notification await this.emailGateway.notifyNewMessage(notificationPayload); this.logger.log(`New message notification sent for user ${userId}, message ${messageId}`); return notificationPayload; } catch (error) { this.logger.error( `Failed to process new message notification for user ${userId}, message ${messageId}: ${ error instanceof Error ? error.message : "Unknown error" }`, ); throw error; } } /** * Handle bulk new messages (useful for initial sync or batch processing) */ async handleBulkNewMessages(userId: string, messageIds: number[], mailboxId?: string) { this.logger.log(`Processing bulk new message notifications for user ${userId}, ${messageIds.length} messages`); const results = []; for (const messageId of messageIds) { try { const result = await this.handleNewMessage(userId, messageId, mailboxId); results.push(result); } catch (error) { this.logger.error(`Failed to process message ${messageId} in bulk notification: ${error}`); } } return results; } /** * Simulate new message arrival (for testing purposes) */ async simulateNewMessage(userId: string, messageData: Partial) { this.logger.log(`Simulating new message for user ${userId}`); const simulatedPayload: EmailNotificationPayload = { messageId: Math.floor(Math.random() * 10000), userId, from: { name: "Test Sender", address: "test@example.com", }, to: [ { name: "Test Recipient", address: "recipient@example.com", }, ], subject: "Test Message", preview: "This is a test message for WebSocket notification", hasAttachments: false, received: new Date().toISOString(), mailboxId: "inbox", mailboxName: "Inbox", isRead: false, isFlagged: false, size: 1024, ...messageData, }; await this.emailGateway.notifyNewMessage(simulatedPayload); return simulatedPayload; } private async getMailboxName(userId: string, mailboxId: string): Promise { try { const mailboxIds = await this.mailboxResolverService.getUserMailboxIds(userId); // Check which mailbox this ID corresponds to if (mailboxIds.inbox === mailboxId) return "Inbox"; if (mailboxIds.sent === mailboxId) return "Sent"; if (mailboxIds.drafts === mailboxId) return "Drafts"; if (mailboxIds.trash === mailboxId) return "Trash"; if (mailboxIds.junk === mailboxId) return "Junk"; if (mailboxIds.archive === mailboxId) return "Archive"; if (mailboxIds.favorite === mailboxId) return "Favorite"; return "Unknown"; } catch (error) { this.logger.error(`Failed to get mailbox name for ${mailboxId}: ${error}`); return "Unknown"; } } private extractPreview(content: string): string { // Remove HTML tags and get first 150 characters const textContent = content.replace(/<[^>]*>/g, "").trim(); return textContent.length > 150 ? textContent.substring(0, 150) + "..." : textContent; } }