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

584 lines
21 KiB
TypeScript

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;
}
}
}