import { Injectable, Logger } from "@nestjs/common"; import { firstValueFrom } from "rxjs"; import { MailboxResolverService } from "./mailbox-resolver.service"; import { EmailMessage } from "../../../common/enums/message.enum"; import { MailServerService } from "../../mail-server/services/mail-server.service"; import { MessageListQueryDto, SearchMessagesQueryDto } from "../DTO/email-query.dto"; import { SendEmailDto, UpdateDraftDto } from "../DTO/send-email.dto"; import { EmailGateway } from "../email.gateway"; import { EmailDeletePayload, EmailMovePayload, EmailStatusUpdatePayload } from "../interfaces/email-events.interface"; @Injectable() export class EmailService { private readonly logger = new Logger(EmailService.name); constructor( private readonly mailServerService: MailServerService, private readonly mailboxResolverService: MailboxResolverService, private readonly emailGateway: EmailGateway, // private readonly userService: UsersService, ) {} //######################################################## async sendEmail(userEmailId: string, sendEmailDto: SendEmailDto) { // const userEmailId = await this.userService.getUserEmailIdFromId(userId); this.logger.log(`Sending email from user ${userEmailId} to ${sendEmailDto.to.map((t) => t.address).join(", ")}`); try { const response = await firstValueFrom(this.mailServerService.submission.submitMessage(userEmailId, sendEmailDto)); this.logger.log(`Email sent successfully: ${response.id} (Queue: ${response.queueId})`); return { message: EmailMessage.EMAIL_SENDING_SUCCESS, messageId: response.id, queueId: response.queueId, from: response.from, to: response.to, subject: response.subject, created: response.created, }; } catch (error) { this.logger.error(`Failed to send email for user ${userEmailId}: ${error instanceof Error ? error.message : "Unknown error"}`); throw error; } } //######################################################## async getEmailStatus(queueId: string) { this.logger.log(`Getting email status for queue: ${queueId}`); try { const response = await firstValueFrom(this.mailServerService.submission.getQueueStatus(queueId)); this.logger.log(`Retrieved status for queue ${queueId}: ${response.status}`); return { message: EmailMessage.EMAIL_STATUS_RETRIEVED_SUCCESSFULLY, ...response, }; } catch (error) { this.logger.error(`Failed to get email status for queue ${queueId}: ${error instanceof Error ? error.message : "Unknown error"}`); throw error; } } //######################################################## async searchMessages(userId: string, query: SearchMessagesQueryDto) { this.logger.log(`Searching messages for user ${userId} with query: ${query.q}`); try { const response = await firstValueFrom( this.mailServerService.messages.searchMessages(userId, { query: query.q || "", limit: query.limit, page: query.page }), ); this.logger.log(`Found ${response.results.length} messages matching search for user: ${userId}`); return { message: EmailMessage.MESSAGES_SEARCHED_SUCCESSFULLY, ...response, }; } catch (error) { this.logger.error(`Failed to search messages for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`); throw error; } } //######################################################## async listMessages(userId: string, query: MessageListQueryDto) { try { const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(userId); this.logger.log(`Listing messages in mailbox ${inboxMailboxId} for user: ${userId}`); const { results, total: count } = await firstValueFrom(this.mailServerService.messages.listMessages(userId, inboxMailboxId, query)); this.logger.log(`Found ${count} messages in mailbox ${inboxMailboxId} for user: ${userId}`); return { message: EmailMessage.MESSAGES_LISTED_SUCCESSFULLY, results, count, paginate: true, }; } catch (error) { this.logger.error(`Failed to list messages for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`); throw error; } } //######################################################## async getMessage(userId: string, messageId: number) { try { const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(userId); this.logger.log(`Getting message ${messageId} from inbox ${inboxMailboxId} for user: ${userId}`); await this.markMessageAsSeen(userId, messageId); const message = await firstValueFrom(this.mailServerService.messages.getMessage(userId, inboxMailboxId, messageId)); this.logger.log(`Retrieved message ${messageId} for user: ${userId}`); return { message, }; } catch (error) { this.logger.error(`Failed to get message ${messageId} for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`); throw error; } } //######################################################## async deleteMessage(userId: string, messageId: number) { try { const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(userId); const result = await firstValueFrom(this.mailServerService.messages.deleteMessage(userId, inboxMailboxId, messageId)); // Emit WebSocket notification const payload: EmailDeletePayload = { messageId, userId, mailboxId: inboxMailboxId, mailboxName: "Inbox", timestamp: new Date().toISOString(), }; await this.emailGateway.notifyMessageDeleted(payload); return { success: true, message: EmailMessage.MESSAGE_DELETED_SUCCESSFULLY, result, }; } catch (error) { this.logger.error(`Failed to delete message ${messageId} for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`); throw error; } } //============================================== async markMessageAsSeen(userId: string, messageId: number) { try { const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(userId); const result = await firstValueFrom(this.mailServerService.messages.updateMessage(userId, inboxMailboxId, messageId, { seen: true })); // Emit WebSocket notification const payload: EmailStatusUpdatePayload = { messageId, userId, status: "read", mailboxId: inboxMailboxId, timestamp: new Date().toISOString(), }; await this.emailGateway.notifyMessageStatusUpdate(payload); return { success: true, message: EmailMessage.MESSAGE_MARKED_AS_SEEN_SUCCESSFULLY, data: result, }; } catch (error) { this.logger.error( `Failed to mark message ${messageId} as seen for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`, ); throw error; } } //============================================== async getSentMessages(userId: string, query: MessageListQueryDto) { try { const sentMailboxId = await this.mailboxResolverService.getSentMailboxId(userId); const { results: sentMails, total: count } = await firstValueFrom(this.mailServerService.messages.listMessages(userId, sentMailboxId, query)); return { sentMails, count, paginate: true, }; } catch (error) { this.logger.error(`Failed to get sent messages for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`); throw error; } } //============================================== async getTrashMessages(userId: string, query: MessageListQueryDto) { try { const trashMailboxId = await this.mailboxResolverService.getTrashMailboxId(userId); const { results: trashMails, total: count } = await firstValueFrom(this.mailServerService.messages.listMessages(userId, trashMailboxId, query)); return { trashMails, count, paginate: true, }; } catch (error) { this.logger.error(`Failed to get trash messages for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`); throw error; } } //============================================== async getJunkMessages(userId: string, query: MessageListQueryDto) { try { const junkMailboxId = await this.mailboxResolverService.getJunkMailboxId(userId); const { results: junkMails, total: count } = await firstValueFrom(this.mailServerService.messages.listMessages(userId, junkMailboxId, query)); return { junkMails, count, paginate: true, }; } catch (error) { this.logger.error(`Failed to get junk messages for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`); throw error; } } //============================================== async getArchivedMessages(userId: string, query: MessageListQueryDto) { try { const archiveMailboxId = await this.mailboxResolverService.getArchiveMailboxId(userId); const { results: archiveMails, total: count } = await firstValueFrom( this.mailServerService.messages.listMessages(userId, archiveMailboxId, query), ); return { archiveMails, count, paginate: true, }; } catch (error) { this.logger.error(`Failed to get archive messages for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`); throw error; } } //============================================== async getFavoriteMessages(userId: string, query: MessageListQueryDto) { try { const favoriteMailboxId = await this.mailboxResolverService.getFavoriteMailboxId(userId); const { results: favoriteMails, total: count } = await firstValueFrom( this.mailServerService.messages.listMessages(userId, favoriteMailboxId, query), ); return { favoriteMails, count, paginate: true, }; } catch (error) { this.logger.error(`Failed to get junk messages for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`); throw error; } } //============================================== async getDraftsMessages(userId: string, query: MessageListQueryDto) { try { const draftsMailboxId = await this.mailboxResolverService.getDraftsMailboxId(userId); const { results: draftsMail, total: count } = await firstValueFrom( this.mailServerService.messages.listMessages(userId, draftsMailboxId, query), ); return { draftsMail, count, paginate: true, }; } catch (error) { this.logger.error(`Failed to get drafts messages for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`); throw error; } } //============================================== async updateDraft(userEmailId: string, messageId: number, updateDraftDto: UpdateDraftDto) { this.logger.log(`Updating draft ${messageId} for user ${userEmailId}`); try { const draftsMailboxId = await this.mailboxResolverService.getDraftsMailboxId(userEmailId); const existingDraft = await firstValueFrom(this.mailServerService.messages.getMessage(userEmailId, draftsMailboxId, messageId)); const draftData: SendEmailDto = { mailbox: updateDraftDto.mailbox, from: updateDraftDto.from || { address: existingDraft.from?.address || "" }, to: updateDraftDto.to || [], cc: updateDraftDto.cc, bcc: updateDraftDto.bcc, replyTo: updateDraftDto.replyTo, headers: updateDraftDto.headers, subject: updateDraftDto.subject, text: updateDraftDto.text, html: updateDraftDto.html, attachments: updateDraftDto.attachments, meta: updateDraftDto.meta, sess: updateDraftDto.sess, ip: updateDraftDto.ip, reference: updateDraftDto.reference, isDraft: true, uploadOnly: true, draft: { mailbox: draftsMailboxId, id: messageId, }, }; const response = await firstValueFrom(this.mailServerService.submission.submitMessage(userEmailId, draftData)); this.logger.log(`Draft updated successfully: ${response.id}`); return { message: EmailMessage.DRAFT_UPDATED_SUCCESSFULLY, messageId: response.id, from: response.from, to: response.to, subject: response.subject || "Draft", updated: response.created, isDraft: true, }; } catch (error) { this.logger.error(`Failed to update draft ${messageId} for user ${userEmailId}: ${error instanceof Error ? error.message : "Unknown error"}`); throw error; } } //============================================== async sendDraft(userId: string, messageId: number) { this.logger.log(`Sending draft ${messageId} for user: ${userId}`); try { const draftsMailboxId = await this.mailboxResolverService.getDraftsMailboxId(userId); const result = await firstValueFrom( this.mailServerService.messages.submitDraft(userId, draftsMailboxId, messageId, { deleteFiles: false, }), ); this.logger.log(`Draft ${messageId} sent successfully`); return { message: EmailMessage.DRAFT_SENT_SUCCESSFULLY, messageId: result.id, queueId: result.queueId, result, }; } catch (error) { this.logger.error(`Failed to send draft ${messageId} for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`); throw error; } } //============================================== async deleteDraft(userId: string, messageId: number) { this.logger.log(`Deleting draft ${messageId} for user: ${userId}`); try { const draftsMailboxId = await this.mailboxResolverService.getDraftsMailboxId(userId); const result = await firstValueFrom(this.mailServerService.messages.deleteMessage(userId, draftsMailboxId, messageId)); this.logger.log(`Draft ${messageId} deleted successfully`); return { message: EmailMessage.DRAFT_DELETED_SUCCESSFULLY, result, }; } catch (error) { this.logger.error(`Failed to delete draft ${messageId} for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`); throw error; } } //============================================== async moveMessageToArchive(userId: string, messageId: number) { this.logger.log(`Moving message ${messageId} to archive for user: ${userId}`); try { const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(userId); const archiveMailboxId = await this.mailboxResolverService.getArchiveMailboxId(userId); const result = await firstValueFrom( this.mailServerService.messages.updateMessage(userId, inboxMailboxId, messageId, { moveTo: archiveMailboxId, }), ); // Emit WebSocket notification const payload: EmailMovePayload = { messageId, userId, fromMailboxId: inboxMailboxId, toMailboxId: archiveMailboxId, fromMailboxName: "Inbox", toMailboxName: "Archive", timestamp: new Date().toISOString(), }; await this.emailGateway.notifyMessageMoved(payload); this.logger.log(`Message ${messageId} moved to archive successfully`); return { success: true, message: EmailMessage.MESSAGE_MOVED_TO_ARCHIVE_SUCCESSFULLY, result, }; } catch (error) { this.logger.error( `Failed to move message ${messageId} to archive for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`, ); throw error; } } //============================================== async moveMessageToFavorite(userId: string, messageId: number) { this.logger.log(`Moving message ${messageId} to favorite for user: ${userId}`); try { const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(userId); const favoriteMailboxId = await this.mailboxResolverService.getFavoriteMailboxId(userId); const result = await firstValueFrom( this.mailServerService.messages.updateMessage(userId, inboxMailboxId, messageId, { moveTo: favoriteMailboxId, }), ); // Emit WebSocket notification const payload: EmailMovePayload = { messageId, userId, fromMailboxId: inboxMailboxId, toMailboxId: favoriteMailboxId, fromMailboxName: "Inbox", toMailboxName: "Favorite", timestamp: new Date().toISOString(), }; await this.emailGateway.notifyMessageMoved(payload); this.logger.log(`Message ${messageId} moved to favorite successfully`); return { success: true, message: EmailMessage.MESSAGE_MOVED_TO_FAVORITE_SUCCESSFULLY, result, }; } catch (error) { this.logger.error( `Failed to move message ${messageId} to favorite for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`, ); throw error; } } async moveMessageToTrash(userId: string, messageId: number) { this.logger.log(`Moving message ${messageId} to trash for user: ${userId}`); try { const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(userId); const trashMailboxId = await this.mailboxResolverService.getTrashMailboxId(userId); const result = await firstValueFrom( this.mailServerService.messages.updateMessage(userId, inboxMailboxId, messageId, { moveTo: trashMailboxId, }), ); this.logger.log(`Message ${messageId} moved to trash successfully`); return { success: true, message: EmailMessage.MESSAGE_MOVED_TO_TRASH_SUCCESSFULLY, result, }; } catch (error) { this.logger.error( `Failed to move message ${messageId} to trash for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`, ); throw error; } } //============================================== async moveMessageToInboxFromArchive(userId: string, messageId: number) { this.logger.log(`Moving message ${messageId} from archive to inbox for user: ${userId}`); try { const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(userId); const archiveMailboxId = await this.mailboxResolverService.getArchiveMailboxId(userId); const result = await firstValueFrom( this.mailServerService.messages.updateMessage(userId, archiveMailboxId, messageId, { moveTo: inboxMailboxId, }), ); // Emit WebSocket notification const payload: EmailMovePayload = { messageId, userId, fromMailboxId: archiveMailboxId, toMailboxId: inboxMailboxId, fromMailboxName: "Archive", toMailboxName: "Inbox", timestamp: new Date().toISOString(), }; await this.emailGateway.notifyMessageMoved(payload); this.logger.log(`Message ${messageId} moved from archive to inbox successfully`); return { success: true, message: EmailMessage.MESSAGE_MOVED_TO_INBOX_FROM_ARCHIVE_SUCCESSFULLY, result, }; } catch (error) { this.logger.error( `Failed to move message ${messageId} from archive to inbox for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`, ); throw error; } } //============================================== async moveMessageToInboxFromFavorite(userId: string, messageId: number) { this.logger.log(`Moving message ${messageId} from favorite to inbox for user: ${userId}`); try { const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(userId); const favoriteMailboxId = await this.mailboxResolverService.getFavoriteMailboxId(userId); const result = await firstValueFrom( this.mailServerService.messages.updateMessage(userId, favoriteMailboxId, messageId, { moveTo: inboxMailboxId, }), ); // Emit WebSocket notification const payload: EmailMovePayload = { messageId, userId, fromMailboxId: favoriteMailboxId, toMailboxId: inboxMailboxId, fromMailboxName: "Favorite", toMailboxName: "Inbox", timestamp: new Date().toISOString(), }; await this.emailGateway.notifyMessageMoved(payload); this.logger.log(`Message ${messageId} moved from favorite to inbox successfully`); return { success: true, message: EmailMessage.MESSAGE_MOVED_TO_INBOX_FROM_FAVORITE_SUCCESSFULLY, result, }; } catch (error) { this.logger.error( `Failed to move message ${messageId} from favorite to inbox for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`, ); throw error; } } }