Files
dmail-api/src/modules/email/services/email.service.ts
T
2025-07-12 12:17:49 +03:30

708 lines
26 KiB
TypeScript

import { BadRequestException, 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 { MailboxEnum } from "../../mailbox/enums/mailbox.enum";
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 messageLocation = await this.findMessageMailbox(userId, messageId);
if (!messageLocation) throw new BadRequestException(EmailMessage.MESSAGE_NOT_FOUND);
this.logger.log(`Getting message ${messageId} from ${messageLocation.mailboxName} mailbox for user: ${userId}`);
await this.markMessageAsSeen(userId, messageId);
const message = await firstValueFrom(this.mailServerService.messages.getMessage(userId, messageLocation.mailboxId, messageId));
this.logger.log(`Retrieved message ${messageId} from ${messageLocation.mailboxName} 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;
}
}
//########################################################
private async findMessageMailbox(userId: string, messageId: number): Promise<{ mailboxId: string; mailboxName: string } | null> {
try {
const mailboxIds = await this.mailboxResolverService.getUserMailboxIds(userId);
const mailboxesToSearch = [
{ id: mailboxIds.inbox, name: MailboxEnum.INBOX },
{ id: mailboxIds.sent, name: MailboxEnum.SENT },
{ id: mailboxIds.drafts, name: MailboxEnum.DRAFTS },
{ id: mailboxIds.trash, name: MailboxEnum.TRASH },
{ id: mailboxIds.archive, name: MailboxEnum.ARCHIVE },
{ id: mailboxIds.favorite, name: MailboxEnum.FAVORITE },
{ id: mailboxIds.junk, name: MailboxEnum.Junk },
];
for (const mailbox of mailboxesToSearch) {
if (!mailbox.id) continue;
try {
await firstValueFrom(this.mailServerService.messages.getMessage(userId, mailbox.id, messageId));
this.logger.log(`Message ${messageId} found in ${mailbox.name} mailbox`);
return { mailboxId: mailbox.id, mailboxName: mailbox.name };
} catch (_error) {
continue;
}
}
this.logger.warn(`Message ${messageId} not found in any mailbox for user ${userId}`);
return null;
} catch (error) {
this.logger.error(`Failed to find message ${messageId} for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`);
throw error;
}
}
//########################################################
async deleteMessage(userId: string, messageId: number) {
try {
const messageLocation = await this.findMessageMailbox(userId, messageId);
if (!messageLocation) throw new BadRequestException(EmailMessage.MESSAGE_NOT_FOUND);
this.logger.log(`Deleting message ${messageId} from ${messageLocation.mailboxName} mailbox for user: ${userId}`);
const result = await firstValueFrom(this.mailServerService.messages.deleteMessage(userId, messageLocation.mailboxId, messageId));
// Emit WebSocket notification
const payload: EmailDeletePayload = {
messageId,
userId,
mailboxId: messageLocation.mailboxId,
mailboxName: messageLocation.mailboxName,
timestamp: new Date().toISOString(),
};
await this.emailGateway.notifyMessageDeleted(payload);
this.logger.log(`Message ${messageId} deleted successfully from ${messageLocation.mailboxName}`);
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 messageLocation = await this.findMessageMailbox(userId, messageId);
if (!messageLocation) throw new BadRequestException(EmailMessage.MESSAGE_NOT_FOUND);
this.logger.log(`Marking message ${messageId} as seen in ${messageLocation.mailboxName} mailbox for user: ${userId}`);
const result = await firstValueFrom(
this.mailServerService.messages.updateMessage(userId, messageLocation.mailboxId, messageId, { seen: true }),
);
// Emit WebSocket notification
const payload: EmailStatusUpdatePayload = {
messageId,
userId,
status: "read",
mailboxId: messageLocation.mailboxId,
timestamp: new Date().toISOString(),
};
await this.emailGateway.notifyMessageStatusUpdate(payload);
this.logger.log(`Message ${messageId} marked as seen successfully in ${messageLocation.mailboxName}`);
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 messageLocation = await this.findMessageMailbox(userId, messageId);
if (!messageLocation) throw new BadRequestException(EmailMessage.MESSAGE_NOT_FOUND);
const archiveMailboxId = await this.mailboxResolverService.getArchiveMailboxId(userId);
this.logger.log(`Moving message ${messageId} from ${messageLocation.mailboxName} to ${MailboxEnum.ARCHIVE} for user: ${userId}`);
const result = await firstValueFrom(
this.mailServerService.messages.updateMessage(userId, messageLocation.mailboxId, messageId, {
moveTo: archiveMailboxId,
}),
);
// Emit WebSocket notification
const payload: EmailMovePayload = {
messageId,
userId,
fromMailboxId: messageLocation.mailboxId,
toMailboxId: archiveMailboxId,
fromMailboxName: messageLocation.mailboxName,
toMailboxName: MailboxEnum.ARCHIVE,
timestamp: new Date().toISOString(),
};
await this.emailGateway.notifyMessageMoved(payload);
this.logger.log(`Message ${messageId} moved to archive successfully from ${messageLocation.mailboxName}`);
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 messageLocation = await this.findMessageMailbox(userId, messageId);
if (!messageLocation) throw new BadRequestException(EmailMessage.MESSAGE_NOT_FOUND);
const favoriteMailboxId = await this.mailboxResolverService.getFavoriteMailboxId(userId);
this.logger.log(`Moving message ${messageId} from ${messageLocation.mailboxName} to ${MailboxEnum.FAVORITE} for user: ${userId}`);
const result = await firstValueFrom(
this.mailServerService.messages.updateMessage(userId, messageLocation.mailboxId, messageId, {
moveTo: favoriteMailboxId,
}),
);
// Emit WebSocket notification
const payload: EmailMovePayload = {
messageId,
userId,
fromMailboxId: messageLocation.mailboxId,
toMailboxId: favoriteMailboxId,
fromMailboxName: messageLocation.mailboxName,
toMailboxName: MailboxEnum.FAVORITE,
timestamp: new Date().toISOString(),
};
await this.emailGateway.notifyMessageMoved(payload);
this.logger.log(`Message ${messageId} moved to favorite successfully from ${messageLocation.mailboxName}`);
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 messageLocation = await this.findMessageMailbox(userId, messageId);
if (!messageLocation) throw new BadRequestException(EmailMessage.MESSAGE_NOT_FOUND);
const trashMailboxId = await this.mailboxResolverService.getTrashMailboxId(userId);
this.logger.log(`Moving message ${messageId} from ${messageLocation.mailboxName} to ${MailboxEnum.TRASH} for user: ${userId}`);
const result = await firstValueFrom(
this.mailServerService.messages.updateMessage(userId, messageLocation.mailboxId, messageId, {
moveTo: trashMailboxId,
}),
);
// Emit WebSocket notification
const payload: EmailMovePayload = {
messageId,
userId,
fromMailboxId: messageLocation.mailboxId,
toMailboxId: trashMailboxId,
fromMailboxName: messageLocation.mailboxName,
toMailboxName: MailboxEnum.TRASH,
timestamp: new Date().toISOString(),
};
await this.emailGateway.notifyMessageMoved(payload);
this.logger.log(`Message ${messageId} moved to trash successfully from ${messageLocation.mailboxName}`);
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;
}
}
//==============================================
async moveMessageToInboxFromTrash(userId: string, messageId: number) {
this.logger.log(`Moving message ${messageId} from trash to inbox 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, trashMailboxId, messageId, {
moveTo: inboxMailboxId,
}),
);
// Emit WebSocket notification
const payload: EmailMovePayload = {
messageId,
userId,
fromMailboxId: trashMailboxId,
toMailboxId: inboxMailboxId,
fromMailboxName: "Trash",
toMailboxName: "Inbox",
timestamp: new Date().toISOString(),
};
await this.emailGateway.notifyMessageMoved(payload);
this.logger.log(`Message ${messageId} moved from trash to inbox successfully`);
return {
success: true,
message: EmailMessage.MESSAGE_MOVED_TO_INBOX_FROM_TRASH_SUCCESSFULLY,
result,
};
} catch (error) {
this.logger.error(
`Failed to move message ${messageId} from trash to inbox for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`,
);
throw error;
}
}
}