chore: add new fetaure

This commit is contained in:
mahyargdz
2025-07-20 14:48:02 +03:30
parent 6dd996b30d
commit b2dd55b166
19 changed files with 654 additions and 193 deletions
+1
View File
@@ -212,6 +212,7 @@ export const enum EmailMessage {
MESSAGE_MOVED_TO_INBOX_FROM_ARCHIVE_SUCCESSFULLY = "پیام با موفقیت از آرشیو به صندوق ورودی منتقل شد",
MESSAGE_MOVED_TO_INBOX_FROM_FAVORITE_SUCCESSFULLY = "پیام با موفقیت از علاقه مندی ها به صندوق ورودی منتقل شد",
MESSAGE_MOVED_TO_INBOX_FROM_TRASH_SUCCESSFULLY = "پیام با موفقیت از سطل زباله به صندوق ورودی منتقل شد",
MESSAGE_MOVED_TO_INBOX_FROM_JUNK_SUCCESSFULLY = "پیام با موفقیت از جانک به صندوق ورودی منتقل شد",
MESSAGE_NOT_FOUND = "پیام یافت نشد",
// Bulk action messages
@@ -11,6 +11,7 @@ export enum BulkActionType {
UNARCHIVE = "unarchive",
RESTORE = "restore",
JUNK = "junk",
NOTJUNK = "notjunk",
TRASH = "trash",
}
+57 -1
View File
@@ -214,10 +214,19 @@ export class EmailController {
return this.emailService.moveMessageToJunk(userId, Number(params.messageId));
}
@Patch("messages/:messageId/not-junk")
@ApiOperation({ summary: "Move a message from junk back to inbox (not spam)" })
@ApiResponse({ status: 200, description: "Message moved from junk to inbox successfully" })
@ApiResponse({ status: 404, description: "Message not found in junk" })
@ApiResponse({ status: 403, description: "Not authorized to move message" })
moveMessageToInboxFromJunk(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto) {
return this.emailService.moveMessageToInboxFromJunk(userId, Number(params.messageId));
}
@Post("messages/bulk-action")
@ApiOperation({
summary: "Perform bulk actions on multiple messages",
description: "Apply actions (seen, delete, archive, unarchive, favorite, unfavorite, junk, trash, restore) to multiple messages at once",
description: "Apply actions (seen, delete, archive, unarchive, favorite, unfavorite, junk, notjunk, trash, restore) to multiple messages at once",
})
@ApiResponse({ status: 200, description: "Bulk action completed successfully" })
@ApiResponse({ status: 206, description: "Bulk action partially completed" })
@@ -226,4 +235,51 @@ export class EmailController {
performBulkAction(@UserDec("wildduckUserId") userId: string, @Body() bulkActionDto: BulkActionDto) {
return this.emailService.performBulkAction(userId, bulkActionDto);
}
// // Domain Access Management Endpoints
// @Get("spam/domains/allowed")
// @ApiOperation({ summary: "List domains in allowlist (never marked as spam)" })
// @ApiResponse({ status: 200, description: "List of allowed domains" })
// listAllowedDomains(@UserDec("id") userId: string, @Query() query: ListDomainAccessQueryDto) {
// return this.emailSpamService.listAllowedDomains(userId, query);
// }
// @Get("spam/domains/blocked")
// @ApiOperation({ summary: "List domains in blocklist (always marked as spam)" })
// @ApiResponse({ status: 200, description: "List of blocked domains" })
// listBlockedDomains(@UserDec("id") userId: string, @Query() query: ListDomainAccessQueryDto) {
// return this.emailSpamService.listBlockedDomains(userId, query);
// }
// @Post("spam/domains/allowed")
// @ApiOperation({ summary: "Add domain to allowlist" })
// @ApiResponse({ status: 201, description: "Domain added to allowlist successfully" })
// @ApiResponse({ status: 400, description: "Invalid domain data" })
// addDomainToAllowlist(@UserDec("id") userId: string, @Body() createDomainDto: CreateDomainAccessDto) {
// return this.emailSpamService.addDomainToAllowlist(createDomainDto.domain, createDomainDto.description, userId);
// }
// @Post("spam/domains/blocked")
// @ApiOperation({ summary: "Add domain to blocklist" })
// @ApiResponse({ status: 201, description: "Domain added to blocklist successfully" })
// @ApiResponse({ status: 400, description: "Invalid domain data" })
// addDomainToBlocklist(@UserDec("id") userId: string, @Body() createDomainDto: CreateDomainAccessDto) {
// return this.emailSpamService.addDomainToBlocklist(createDomainDto.domain, createDomainDto.description, userId);
// }
// @Delete("spam/domains/allowed/:domain")
// @ApiOperation({ summary: "Remove domain from allowlist" })
// @ApiResponse({ status: 200, description: "Domain removed from allowlist successfully" })
// @ApiResponse({ status: 404, description: "Domain not found in allowlist" })
// removeDomainFromAllowlist(@UserDec("id") userId: string, @Param("domain") domain: string) {
// return this.emailSpamService.removeDomainFromAllowlist(domain, userId);
// }
// @Delete("spam/domains/blocked/:domain")
// @ApiOperation({ summary: "Remove domain from blocklist" })
// @ApiResponse({ status: 200, description: "Domain removed from blocklist successfully" })
// @ApiResponse({ status: 404, description: "Domain not found in blocklist" })
// removeDomainFromBlocklist(@UserDec("id") userId: string, @Param("domain") domain: string) {
// return this.emailSpamService.removeDomainFromBlocklist(domain, userId);
// }
}
+13 -3
View File
@@ -4,21 +4,22 @@ import { Module } from "@nestjs/common";
import { JwtModule } from "@nestjs/jwt";
// import { EmailGatewayController } from "./controllers/email-gateway.controller";
import { EMAIL_QUEUE_CONSTANTS } from "./constants/email-events.constant";
import { EmailController } from "./email.controller";
import { EmailGateway } from "./gateways/email.gateway";
import { EmailMonitoringProcessor } from "./queue/email-monitoring.processor";
import { EmailHeadersService } from "./services/email-headers.service";
import { EmailMonitoringService } from "./services/email-monitoring.service";
import { EmailNotificationService } from "./services/email-notification.service";
import { EmailSpamService } from "./services/email-spam.service";
import { EmailService } from "./services/email.service";
import { MailboxResolverService } from "./services/mailbox-resolver.service";
import { jwtConfig } from "../../configs/jwt.config";
import { MailServerModule } from "../mail-server/mail-server.module";
import { TemplatesModule } from "../templates/templates.module";
import { WebSocketAuthService } from "./services/websocket-auth.service";
import { User } from "../users/entities/user.entity";
import { UsersModule } from "../users/users.module";
import { EMAIL_QUEUE_CONSTANTS } from "./constants/email-events.constant";
import { WebSocketAuthService } from "./services/websocket-auth.service";
@Module({
imports: [
@@ -34,6 +35,7 @@ import { WebSocketAuthService } from "./services/websocket-auth.service";
controllers: [EmailController],
providers: [
EmailService,
EmailSpamService,
MailboxResolverService,
EmailHeadersService,
EmailGateway,
@@ -42,6 +44,14 @@ import { WebSocketAuthService } from "./services/websocket-auth.service";
EmailMonitoringProcessor,
WebSocketAuthService,
],
exports: [EmailService, MailboxResolverService, EmailHeadersService, EmailGateway, EmailMonitoringService, EmailNotificationService],
exports: [
EmailService,
EmailSpamService,
MailboxResolverService,
EmailHeadersService,
EmailGateway,
EmailMonitoringService,
EmailNotificationService,
],
})
export class EmailModule {}
@@ -0,0 +1,19 @@
export interface SpamActionOptions {
messageId: number;
wildduckUserId: string;
/**
* Whether to automatically add the sender's domain to allowlist/blocklist
* @default false
*/
manageDomainList?: boolean;
/**
* Tag to use for domain access control (defaults to user's business ID or 'default')
*/
domainTag?: string;
}
export interface MessageSenderInfo {
senderDomain: string;
senderEmail: string;
messageId: number;
}
@@ -0,0 +1,208 @@
import { Injectable, Logger } from "@nestjs/common";
import { firstValueFrom } from "rxjs";
import { EmailMessage } from "../../../common/enums/message.enum";
import { ListDomainAccessQueryDto } from "../../mail-server/DTO/domain-access.dto";
import { MailServerService } from "../../mail-server/services/mail-server.service";
import { MessageSenderInfo, SpamActionOptions } from "../interfaces/email-spam.interface";
@Injectable()
export class EmailSpamService {
private readonly logger = new Logger(EmailSpamService.name);
constructor(private readonly mailServerService: MailServerService) {}
async getMessageSenderInfo(userId: string, messageId: number): Promise<MessageSenderInfo | null> {
try {
const mailboxes = await firstValueFrom(this.mailServerService.mailboxes.listMailboxes(userId));
for (const mailbox of mailboxes.results) {
try {
const message = await firstValueFrom(this.mailServerService.messages.getMessage(userId, mailbox.id, messageId));
if (message && message.from && message.from.address) {
const senderEmail = message.from.address;
const senderDomain = senderEmail.split("@")[1]?.toLowerCase();
if (senderDomain) {
return {
senderDomain,
senderEmail,
messageId,
};
}
}
} catch (_error) {
continue;
}
}
return null;
} catch (error) {
this.logger.error(`Failed to get sender info for message ${messageId}: ${error instanceof Error ? error.message : "Unknown error"}`);
throw error;
}
}
//==============================================
async markAsSpam(options: SpamActionOptions) {
const { messageId, wildduckUserId, manageDomainList = false, domainTag = "default" } = options;
this.logger.log(`Marking message ${messageId} as spam for user: ${wildduckUserId}`);
try {
let domainBlocked = false;
if (manageDomainList) {
const senderInfo = await this.getMessageSenderInfo(wildduckUserId, messageId);
if (senderInfo) {
try {
await firstValueFrom(
this.mailServerService.domainAccess.addBlockedDomain(domainTag, {
domain: senderInfo.senderDomain,
description: `Auto-blocked due to spam report from message ${messageId}`,
}),
);
domainBlocked = true;
this.logger.log(`Added domain ${senderInfo.senderDomain} to blocklist for tag: ${domainTag}`);
} catch (error) {
this.logger.warn(`Could not add domain to blocklist: ${error instanceof Error ? error.message : "Unknown error"}`);
}
}
}
return {
success: true,
message: EmailMessage.MESSAGE_MOVED_TO_JUNK_SUCCESSFULLY,
domainBlocked,
};
} catch (error) {
this.logger.error(`Failed to mark message ${messageId} as spam: ${error instanceof Error ? error.message : "Unknown error"}`);
throw error;
}
}
//==============================================
async markAsNotSpam(options: SpamActionOptions) {
const { messageId, wildduckUserId, manageDomainList = false, domainTag = "default" } = options;
this.logger.log(`Marking message ${messageId} as not spam for user: ${wildduckUserId}`);
try {
let domainAllowed = false;
if (manageDomainList) {
const senderInfo = await this.getMessageSenderInfo(wildduckUserId, messageId);
if (senderInfo) {
try {
await firstValueFrom(
this.mailServerService.domainAccess.addAllowedDomain(domainTag, {
domain: senderInfo.senderDomain,
description: `Auto-allowed due to not-spam report from message ${messageId}`,
}),
);
domainAllowed = true;
this.logger.log(`Added domain ${senderInfo.senderDomain} to allowlist for tag: ${domainTag}`);
} catch (error) {
this.logger.warn(`Could not add domain to allowlist: ${error instanceof Error ? error.message : "Unknown error"}`);
}
}
}
return {
success: true,
message: EmailMessage.MESSAGE_MOVED_TO_INBOX_FROM_JUNK_SUCCESSFULLY,
domainAllowed,
};
} catch (error) {
this.logger.error(`Failed to mark message ${messageId} as not spam: ${error instanceof Error ? error.message : "Unknown error"}`);
throw error;
}
}
//==============================================
async listAllowedDomains(domainTag = "default", query?: ListDomainAccessQueryDto) {
this.logger.log(`Listing allowed domains for tag: ${domainTag}`);
try {
return await firstValueFrom(this.mailServerService.domainAccess.listAllowedDomains(domainTag, query));
} catch (error) {
this.logger.error(`Failed to list allowed domains: ${error instanceof Error ? error.message : "Unknown error"}`);
throw error;
}
}
//==============================================
async listBlockedDomains(domainTag = "default", query?: ListDomainAccessQueryDto) {
this.logger.log(`Listing blocked domains for tag: ${domainTag}`);
try {
return await firstValueFrom(this.mailServerService.domainAccess.listBlockedDomains(domainTag, query));
} catch (error) {
this.logger.error(`Failed to list blocked domains: ${error instanceof Error ? error.message : "Unknown error"}`);
throw error;
}
}
//==============================================
async removeDomainFromAllowlist(domain: string, domainTag = "default") {
this.logger.log(`Removing domain ${domain} from allowlist for tag: ${domainTag}`);
try {
return await firstValueFrom(this.mailServerService.domainAccess.removeAllowedDomain(domainTag, domain));
} catch (error) {
this.logger.error(`Failed to remove domain from allowlist: ${error instanceof Error ? error.message : "Unknown error"}`);
throw error;
}
}
//==============================================
async removeDomainFromBlocklist(domain: string, domainTag = "default") {
this.logger.log(`Removing domain ${domain} from blocklist for tag: ${domainTag}`);
try {
return await firstValueFrom(this.mailServerService.domainAccess.removeBlockedDomain(domainTag, domain));
} catch (error) {
this.logger.error(`Failed to remove domain from blocklist: ${error instanceof Error ? error.message : "Unknown error"}`);
throw error;
}
}
//==============================================
async addDomainToAllowlist(domain: string, description?: string, domainTag = "default") {
this.logger.log(`Adding domain ${domain} to allowlist for tag: ${domainTag}`);
try {
return await firstValueFrom(
this.mailServerService.domainAccess.addAllowedDomain(domainTag, {
domain,
description: description || `Manually added to allowlist`,
}),
);
} catch (error) {
this.logger.error(`Failed to add domain to allowlist: ${error instanceof Error ? error.message : "Unknown error"}`);
throw error;
}
}
//==============================================
async addDomainToBlocklist(domain: string, description?: string, domainTag = "default") {
this.logger.log(`Adding domain ${domain} to blocklist for tag: ${domainTag}`);
try {
return await firstValueFrom(
this.mailServerService.domainAccess.addBlockedDomain(domainTag, {
domain,
description: description || `Manually added to blocklist`,
}),
);
} catch (error) {
this.logger.error(`Failed to add domain to blocklist: ${error instanceof Error ? error.message : "Unknown error"}`);
throw error;
}
}
}
+216 -150
View File
@@ -3,6 +3,7 @@ import { firstValueFrom } from "rxjs";
import { EmailHeadersService } from "./email-headers.service";
import { EmailNotificationService } from "./email-notification.service";
import { EmailSpamService } from "./email-spam.service";
import { MailboxResolverService } from "./mailbox-resolver.service";
import { EmailMessage } from "../../../common/enums/message.enum";
import { MailServerService } from "../../mail-server/services/mail-server.service";
@@ -26,14 +27,15 @@ export class EmailService {
private readonly templateProcessorService: TemplateProcessorService,
private readonly userRepository: UserRepository,
private readonly emailHeadersService: EmailHeadersService,
private readonly emailSpamService: EmailSpamService,
) {}
//########################################################
async sendEmail(userEmailId: string, userId: string, sendEmailDto: SendEmailDto) {
this.logger.log(`Sending ${EmailType.GENERAL} email from user ${userEmailId} to ${sendEmailDto.to.map((t) => t.address).join(", ")}`);
async sendEmail(wildduckUserId: string, userId: string, sendEmailDto: SendEmailDto) {
this.logger.log(`Sending ${EmailType.GENERAL} email from user ${wildduckUserId} to ${sendEmailDto.to.map((t) => t.address).join(", ")}`);
try {
const user = await this.userRepository.findOne({ wildduckUserId: userEmailId, deletedAt: null }, { populate: ["business"] });
const user = await this.userRepository.findOne({ wildduckUserId, deletedAt: null }, { populate: ["business"] });
if (!user) throw new BadRequestException(EmailMessage.USER_NOT_FOUND);
this.logger.log(`Processing email through business template for business: ${user.business.id}`);
@@ -64,14 +66,14 @@ export class EmailService {
sendEmailDto.headers = [...headers];
const response = await firstValueFrom(this.mailServerService.submission.submitMessage(userEmailId, sendEmailDto));
const response = await firstValueFrom(this.mailServerService.submission.submitMessage(wildduckUserId, sendEmailDto));
this.logger.log(`Email sent successfully: ${response.message.id} (Queue: ${response.message.queueId})`);
const sentMailboxId = await this.mailboxResolverService.getSentMailboxId(userId);
const sentMailboxId = await this.mailboxResolverService.getSentMailboxId(wildduckUserId);
await this.emailNotificationService.notifyEmailSent({
userId,
userId: wildduckUserId,
messageId: parseInt(response.message.id),
mailboxId: sentMailboxId,
recipients: sendEmailDto.to,
@@ -85,7 +87,7 @@ export class EmailService {
queueId: response.message.queueId,
};
} catch (error) {
this.logger.error(`Failed to send email for user ${userEmailId}: ${error instanceof Error ? error.message : "Unknown error"}`);
this.logger.error(`Failed to send email for user ${wildduckUserId}: ${error instanceof Error ? error.message : "Unknown error"}`);
throw error;
}
}
@@ -109,8 +111,8 @@ export class EmailService {
}
//########################################################
async searchMessages(userId: string, query: SearchMessagesQueryDto) {
this.logger.log(`Searching messages for user ${userId} with query: ${query.q}`);
async searchMessages(wildduckUserId: string, query: SearchMessagesQueryDto) {
this.logger.log(`Searching messages for user ${wildduckUserId} with query: ${query.q}`);
try {
// Transform query parameters for WildDuck API compatibility
@@ -128,10 +130,10 @@ export class EmailService {
this.logger.debug("Search query for WildDuck:", wildDuckQuery);
const response = await firstValueFrom(this.mailServerService.messages.searchMessages(userId, wildDuckQuery));
const response = await firstValueFrom(this.mailServerService.messages.searchMessages(wildduckUserId, wildDuckQuery));
this.logger.log(
`Found ${response.results.length} messages (${response.total} total, page ${response.page}) matching search for user: ${userId}`,
`Found ${response.results.length} messages (${response.total} total, page ${response.page}) matching search for user: ${wildduckUserId}`,
);
return {
@@ -143,22 +145,22 @@ export class EmailService {
query: response.query,
};
} catch (error) {
this.logger.error(`Failed to search messages for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`);
this.logger.error(`Failed to search messages for user ${wildduckUserId}: ${error instanceof Error ? error.message : "Unknown error"}`);
throw error;
}
}
//########################################################
async listMessages(userId: string, query: MessageListQueryDto) {
async listMessages(wildduckUserId: string, query: MessageListQueryDto) {
try {
const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(userId);
this.logger.log(`Listing messages in mailbox ${inboxMailboxId} for user: ${userId}`);
const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(wildduckUserId);
this.logger.log(`Listing messages in mailbox ${inboxMailboxId} for user: ${wildduckUserId}`);
const wildDuckQuery = this.transformPaginationQuery(query);
const response = await firstValueFrom(this.mailServerService.messages.listMessages(userId, inboxMailboxId, wildDuckQuery));
const response = await firstValueFrom(this.mailServerService.messages.listMessages(wildduckUserId, inboxMailboxId, wildDuckQuery));
this.logger.log(`Found ${response.total} total messages (page ${response.page}) in mailbox ${inboxMailboxId} for user: ${userId}`);
this.logger.log(`Found ${response.total} total messages (page ${response.page}) in mailbox ${inboxMailboxId} for user: ${wildduckUserId}`);
return {
results: response.results,
@@ -169,44 +171,44 @@ export class EmailService {
paginate: true,
};
} catch (error) {
this.logger.error(`Failed to list messages for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`);
this.logger.error(`Failed to list messages for user ${wildduckUserId}: ${error instanceof Error ? error.message : "Unknown error"}`);
throw error;
}
}
//########################################################
async getMessage(userId: string, messageId: number) {
async getMessage(wildduckUserId: string, messageId: number) {
try {
const messageLocation = await this.findMessageMailbox(userId, messageId);
const messageLocation = await this.findMessageMailbox(wildduckUserId, messageId);
if (!messageLocation) throw new BadRequestException(EmailMessage.MESSAGE_NOT_FOUND);
this.logger.log(`Getting message ${messageId} from ${messageLocation.mailboxName} mailbox for user: ${userId}`);
this.logger.log(`Getting message ${messageId} from ${messageLocation.mailboxName} mailbox for user: ${wildduckUserId}`);
await this.markMessageAsSeen(userId, messageId);
const message = await firstValueFrom(this.mailServerService.messages.getMessage(userId, messageLocation.mailboxId, messageId));
await this.markMessageAsSeen(wildduckUserId, messageId);
const message = await firstValueFrom(this.mailServerService.messages.getMessage(wildduckUserId, messageLocation.mailboxId, messageId));
this.logger.log(`Retrieved message ${messageId} from ${messageLocation.mailboxName} for user: ${userId}`);
this.logger.log(`Retrieved message ${messageId} from ${messageLocation.mailboxName} for user: ${wildduckUserId}`);
return {
message,
};
} catch (error) {
this.logger.error(`Failed to get message ${messageId} for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`);
this.logger.error(`Failed to get message ${messageId} for user ${wildduckUserId}: ${error instanceof Error ? error.message : "Unknown error"}`);
throw error;
}
}
//########################################################
async deleteMessage(userId: string, messageId: number) {
async deleteMessage(wildduckUserId: string, messageId: number) {
try {
const messageLocation = await this.findMessageMailbox(userId, messageId);
const messageLocation = await this.findMessageMailbox(wildduckUserId, messageId);
if (!messageLocation) throw new BadRequestException(EmailMessage.MESSAGE_NOT_FOUND);
this.logger.log(`Deleting message ${messageId} from ${messageLocation.mailboxName} mailbox for user: ${userId}`);
this.logger.log(`Deleting message ${messageId} from ${messageLocation.mailboxName} mailbox for user: ${wildduckUserId}`);
const result = await firstValueFrom(this.mailServerService.messages.deleteMessage(userId, messageLocation.mailboxId, messageId));
const result = await firstValueFrom(this.mailServerService.messages.deleteMessage(wildduckUserId, messageLocation.mailboxId, messageId));
this.logger.log(`Message ${messageId} deleted successfully from ${messageLocation.mailboxName}`);
@@ -215,17 +217,19 @@ export class EmailService {
result,
};
} catch (error) {
this.logger.error(`Failed to delete message ${messageId} for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`);
this.logger.error(
`Failed to delete message ${messageId} for user ${wildduckUserId}: ${error instanceof Error ? error.message : "Unknown error"}`,
);
throw error;
}
}
//==============================================
async deleteAllMessages(userId: string, mailboxId: string) {
async deleteAllMessages(wildduckUserId: string, mailboxId: string) {
try {
const result = await firstValueFrom(this.mailServerService.messages.deleteAllFromMailbox(userId, mailboxId));
const result = await firstValueFrom(this.mailServerService.messages.deleteAllFromMailbox(wildduckUserId, mailboxId));
this.logger.log(`All messages deleted successfully from ${mailboxId} for user: ${userId}`);
this.logger.log(`All messages deleted successfully from ${mailboxId} for user: ${wildduckUserId}`);
return {
message: EmailMessage.ALL_MESSAGES_DELETED_SUCCESSFULLY,
@@ -233,28 +237,28 @@ export class EmailService {
result,
};
} catch (error) {
this.logger.error(`Failed to delete all messages for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`);
this.logger.error(`Failed to delete all messages for user ${wildduckUserId}: ${error instanceof Error ? error.message : "Unknown error"}`);
throw error;
}
}
//==============================================
async markMessageAsSeen(userId: string, messageId: number) {
async markMessageAsSeen(wildduckUserId: string, messageId: number) {
try {
const messageLocation = await this.findMessageMailbox(userId, messageId);
const messageLocation = await this.findMessageMailbox(wildduckUserId, 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}`);
this.logger.log(`Marking message ${messageId} as seen in ${messageLocation.mailboxName} mailbox for user: ${wildduckUserId}`);
const { updated } = await firstValueFrom(
this.mailServerService.messages.updateMessage(userId, messageLocation.mailboxId, messageId, { seen: true }),
this.mailServerService.messages.updateMessage(wildduckUserId, messageLocation.mailboxId, messageId, { seen: true }),
);
this.logger.log(`Message ${messageId} marked as seen successfully in ${messageLocation.mailboxName}`);
await this.emailNotificationService.notifyEmailRead({
userId,
userId: wildduckUserId,
messageId,
mailboxId: messageLocation.mailboxId,
});
@@ -266,20 +270,20 @@ export class EmailService {
};
} catch (error) {
this.logger.error(
`Failed to mark message ${messageId} as seen for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`,
`Failed to mark message ${messageId} as seen for user ${wildduckUserId}: ${error instanceof Error ? error.message : "Unknown error"}`,
);
throw error;
}
}
//==============================================
async getSentMessages(userId: string, query: MessageListQueryDto) {
async getSentMessages(wildduckUserId: string, query: MessageListQueryDto) {
try {
const sentMailboxId = await this.mailboxResolverService.getSentMailboxId(userId);
const sentMailboxId = await this.mailboxResolverService.getSentMailboxId(wildduckUserId);
const wildDuckQuery = this.transformPaginationQuery(query);
const response = await firstValueFrom(this.mailServerService.messages.listMessages(userId, sentMailboxId, wildDuckQuery));
const response = await firstValueFrom(this.mailServerService.messages.listMessages(wildduckUserId, sentMailboxId, wildDuckQuery));
this.logger.log(`Found ${response.total} sent messages (page ${response.page}) for user: ${userId}`);
this.logger.log(`Found ${response.total} sent messages (page ${response.page}) for user: ${wildduckUserId}`);
return {
sentMails: response.results,
@@ -290,19 +294,19 @@ export class EmailService {
paginate: true,
};
} catch (error) {
this.logger.error(`Failed to get sent messages for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`);
this.logger.error(`Failed to get sent messages for user ${wildduckUserId}: ${error instanceof Error ? error.message : "Unknown error"}`);
throw error;
}
}
//==============================================
async getTrashMessages(userId: string, query: MessageListQueryDto) {
async getTrashMessages(wildduckUserId: string, query: MessageListQueryDto) {
try {
const trashMailboxId = await this.mailboxResolverService.getTrashMailboxId(userId);
const trashMailboxId = await this.mailboxResolverService.getTrashMailboxId(wildduckUserId);
const wildDuckQuery = this.transformPaginationQuery(query);
const response = await firstValueFrom(this.mailServerService.messages.listMessages(userId, trashMailboxId, wildDuckQuery));
const response = await firstValueFrom(this.mailServerService.messages.listMessages(wildduckUserId, trashMailboxId, wildDuckQuery));
this.logger.log(`Found ${response.total} trash messages (page ${response.page}) for user: ${userId}`);
this.logger.log(`Found ${response.total} trash messages (page ${response.page}) for user: ${wildduckUserId}`);
return {
trashMails: response.results,
@@ -313,19 +317,19 @@ export class EmailService {
paginate: true,
};
} catch (error) {
this.logger.error(`Failed to get trash messages for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`);
this.logger.error(`Failed to get trash messages for user ${wildduckUserId}: ${error instanceof Error ? error.message : "Unknown error"}`);
throw error;
}
}
//==============================================
async getJunkMessages(userId: string, query: MessageListQueryDto) {
async getJunkMessages(wildduckUserId: string, query: MessageListQueryDto) {
try {
const junkMailboxId = await this.mailboxResolverService.getJunkMailboxId(userId);
const junkMailboxId = await this.mailboxResolverService.getJunkMailboxId(wildduckUserId);
const wildDuckQuery = this.transformPaginationQuery(query);
const response = await firstValueFrom(this.mailServerService.messages.listMessages(userId, junkMailboxId, wildDuckQuery));
const response = await firstValueFrom(this.mailServerService.messages.listMessages(wildduckUserId, junkMailboxId, wildDuckQuery));
this.logger.log(`Found ${response.total} junk messages (page ${response.page}) for user: ${userId}`);
this.logger.log(`Found ${response.total} junk messages (page ${response.page}) for user: ${wildduckUserId}`);
return {
junkMails: response.results,
@@ -336,19 +340,19 @@ export class EmailService {
paginate: true,
};
} catch (error) {
this.logger.error(`Failed to get junk messages for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`);
this.logger.error(`Failed to get junk messages for user ${wildduckUserId}: ${error instanceof Error ? error.message : "Unknown error"}`);
throw error;
}
}
//==============================================
async getArchivedMessages(userId: string, query: MessageListQueryDto) {
async getArchivedMessages(wildduckUserId: string, query: MessageListQueryDto) {
try {
const archiveMailboxId = await this.mailboxResolverService.getArchiveMailboxId(userId);
const archiveMailboxId = await this.mailboxResolverService.getArchiveMailboxId(wildduckUserId);
const wildDuckQuery = this.transformPaginationQuery(query);
const response = await firstValueFrom(this.mailServerService.messages.listMessages(userId, archiveMailboxId, wildDuckQuery));
const response = await firstValueFrom(this.mailServerService.messages.listMessages(wildduckUserId, archiveMailboxId, wildDuckQuery));
this.logger.log(`Found ${response.total} archived messages (page ${response.page}) for user: ${userId}`);
this.logger.log(`Found ${response.total} archived messages (page ${response.page}) for user: ${wildduckUserId}`);
return {
archiveMails: response.results,
@@ -359,19 +363,19 @@ export class EmailService {
paginate: true,
};
} catch (error) {
this.logger.error(`Failed to get archive messages for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`);
this.logger.error(`Failed to get archive messages for user ${wildduckUserId}: ${error instanceof Error ? error.message : "Unknown error"}`);
throw error;
}
}
//==============================================
async getFavoriteMessages(userId: string, query: MessageListQueryDto) {
async getFavoriteMessages(wildduckUserId: string, query: MessageListQueryDto) {
try {
const favoriteMailboxId = await this.mailboxResolverService.getFavoriteMailboxId(userId);
const favoriteMailboxId = await this.mailboxResolverService.getFavoriteMailboxId(wildduckUserId);
const wildDuckQuery = this.transformPaginationQuery(query);
const response = await firstValueFrom(this.mailServerService.messages.listMessages(userId, favoriteMailboxId, wildDuckQuery));
const response = await firstValueFrom(this.mailServerService.messages.listMessages(wildduckUserId, favoriteMailboxId, wildDuckQuery));
this.logger.log(`Found ${response.total} favorite messages (page ${response.page}) for user: ${userId}`);
this.logger.log(`Found ${response.total} favorite messages (page ${response.page}) for user: ${wildduckUserId}`);
return {
favoriteMails: response.results,
@@ -382,20 +386,20 @@ export class EmailService {
paginate: true,
};
} catch (error) {
this.logger.error(`Failed to get favorite messages for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`);
this.logger.error(`Failed to get favorite messages for user ${wildduckUserId}: ${error instanceof Error ? error.message : "Unknown error"}`);
throw error;
}
}
//==============================================
async getDraftsMessages(userId: string, query: MessageListQueryDto) {
async getDraftsMessages(wildduckUserId: string, query: MessageListQueryDto) {
try {
const draftsMailboxId = await this.mailboxResolverService.getDraftsMailboxId(userId);
const draftsMailboxId = await this.mailboxResolverService.getDraftsMailboxId(wildduckUserId);
const wildDuckQuery = this.transformPaginationQuery(query);
const response = await firstValueFrom(this.mailServerService.messages.listMessages(userId, draftsMailboxId, wildDuckQuery));
const response = await firstValueFrom(this.mailServerService.messages.listMessages(wildduckUserId, draftsMailboxId, wildDuckQuery));
this.logger.log(`Found ${response.total} draft messages (page ${response.page}) for user: ${userId}`);
this.logger.log(`Found ${response.total} draft messages (page ${response.page}) for user: ${wildduckUserId}`);
return {
draftsMail: response.results,
@@ -406,18 +410,18 @@ export class EmailService {
paginate: true,
};
} catch (error) {
this.logger.error(`Failed to get drafts messages for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`);
this.logger.error(`Failed to get drafts messages for user ${wildduckUserId}: ${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}`);
async updateDraft(wildduckUserId: string, messageId: number, updateDraftDto: UpdateDraftDto) {
this.logger.log(`Updating draft ${messageId} for user ${wildduckUserId}`);
try {
const draftsMailboxId = await this.mailboxResolverService.getDraftsMailboxId(userEmailId);
const existingDraft = await firstValueFrom(this.mailServerService.messages.getMessage(userEmailId, draftsMailboxId, messageId));
const draftsMailboxId = await this.mailboxResolverService.getDraftsMailboxId(wildduckUserId);
const existingDraft = await firstValueFrom(this.mailServerService.messages.getMessage(wildduckUserId, draftsMailboxId, messageId));
const draftData: SendEmailDto = {
// mailbox: updateDraftDto.mailbox,
@@ -444,7 +448,7 @@ export class EmailService {
};
// Process draft content through business template if available
const user = await this.userRepository.findOne({ wildduckUserId: userEmailId, deletedAt: null }, { populate: ["business"] });
const user = await this.userRepository.findOne({ wildduckUserId, deletedAt: null }, { populate: ["business"] });
if (user && user.business && (updateDraftDto.text || updateDraftDto.html)) {
const processedTemplate = await this.templateProcessorService.processEmailContent(user.business.id, user.id, {
@@ -466,7 +470,7 @@ export class EmailService {
}
}
const response = await firstValueFrom(this.mailServerService.submission.submitMessage(userEmailId, draftData));
const response = await firstValueFrom(this.mailServerService.submission.submitMessage(wildduckUserId, draftData));
this.logger.log(`Draft updated successfully: ${response.message.mailbox}`);
@@ -477,44 +481,44 @@ export class EmailService {
isDraft: true,
};
} catch (error) {
this.logger.error(`Failed to update draft ${messageId} for user ${userEmailId}: ${error instanceof Error ? error.message : "Unknown error"}`);
this.logger.error(
`Failed to update draft ${messageId} for user ${wildduckUserId}: ${error instanceof Error ? error.message : "Unknown error"}`,
);
throw error;
}
}
//==============================================
async sendDraft(userId: string, messageId: number) {
this.logger.log(`Sending draft ${messageId} for user: ${userId}`);
async sendDraft(wildduckUserId: string, messageId: number) {
this.logger.log(`Sending draft ${messageId} for user: ${wildduckUserId}`);
try {
const draftsMailboxId = await this.mailboxResolverService.getDraftsMailboxId(userId);
const draftsMailboxId = await this.mailboxResolverService.getDraftsMailboxId(wildduckUserId);
const result = await firstValueFrom(
this.mailServerService.messages.submitDraft(userId, draftsMailboxId, messageId, {
deleteFiles: false,
}),
this.mailServerService.messages.submitDraft(wildduckUserId, draftsMailboxId, messageId, { deleteFiles: false }),
);
this.logger.log(`Draft ${messageId} sent successfully`);
return {
message: EmailMessage.DRAFT_SENT_SUCCESSFULLY,
messageId: result.id,
messageId: result.message.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"}`);
this.logger.error(`Failed to send draft ${messageId} for user ${wildduckUserId}: ${error instanceof Error ? error.message : "Unknown error"}`);
throw error;
}
}
//==============================================
async deleteDraft(userId: string, messageId: number) {
this.logger.log(`Deleting draft ${messageId} for user: ${userId}`);
async deleteDraft(wildduckUserId: string, messageId: number) {
this.logger.log(`Deleting draft ${messageId} for user: ${wildduckUserId}`);
try {
const draftsMailboxId = await this.mailboxResolverService.getDraftsMailboxId(userId);
const result = await firstValueFrom(this.mailServerService.messages.deleteMessage(userId, draftsMailboxId, messageId));
const draftsMailboxId = await this.mailboxResolverService.getDraftsMailboxId(wildduckUserId);
const result = await firstValueFrom(this.mailServerService.messages.deleteMessage(wildduckUserId, draftsMailboxId, messageId));
this.logger.log(`Draft ${messageId} deleted successfully`);
@@ -523,26 +527,28 @@ export class EmailService {
result,
};
} catch (error) {
this.logger.error(`Failed to delete draft ${messageId} for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`);
this.logger.error(
`Failed to delete draft ${messageId} for user ${wildduckUserId}: ${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}`);
async moveMessageToArchive(wildduckUserId: string, messageId: number) {
this.logger.log(`Moving message ${messageId} to archive for user: ${wildduckUserId}`);
try {
const messageLocation = await this.findMessageMailbox(userId, messageId);
const messageLocation = await this.findMessageMailbox(wildduckUserId, messageId);
if (!messageLocation) throw new BadRequestException(EmailMessage.MESSAGE_NOT_FOUND);
const archiveMailboxId = await this.mailboxResolverService.getArchiveMailboxId(userId);
const archiveMailboxId = await this.mailboxResolverService.getArchiveMailboxId(wildduckUserId);
this.logger.log(`Moving message ${messageId} from ${messageLocation.mailboxName} to ${MailboxEnum.ARCHIVE} for user: ${userId}`);
this.logger.log(`Moving message ${messageId} from ${messageLocation.mailboxName} to ${MailboxEnum.ARCHIVE} for user: ${wildduckUserId}`);
const result = await firstValueFrom(
this.mailServerService.messages.updateMessage(userId, messageLocation.mailboxId, messageId, {
this.mailServerService.messages.updateMessage(wildduckUserId, messageLocation.mailboxId, messageId, {
moveTo: archiveMailboxId,
}),
);
@@ -556,27 +562,27 @@ export class EmailService {
};
} catch (error) {
this.logger.error(
`Failed to move message ${messageId} to archive for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`,
`Failed to move message ${messageId} to archive for user ${wildduckUserId}: ${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}`);
async moveMessageToFavorite(wildduckUserId: string, messageId: number) {
this.logger.log(`Moving message ${messageId} to favorite for user: ${wildduckUserId}`);
try {
const messageLocation = await this.findMessageMailbox(userId, messageId);
const messageLocation = await this.findMessageMailbox(wildduckUserId, messageId);
if (!messageLocation) throw new BadRequestException(EmailMessage.MESSAGE_NOT_FOUND);
const favoriteMailboxId = await this.mailboxResolverService.getFavoriteMailboxId(userId);
const favoriteMailboxId = await this.mailboxResolverService.getFavoriteMailboxId(wildduckUserId);
this.logger.log(`Moving message ${messageId} from ${messageLocation.mailboxName} to ${MailboxEnum.FAVORITE} for user: ${userId}`);
this.logger.log(`Moving message ${messageId} from ${messageLocation.mailboxName} to ${MailboxEnum.FAVORITE} for user: ${wildduckUserId}`);
const result = await firstValueFrom(
this.mailServerService.messages.updateMessage(userId, messageLocation.mailboxId, messageId, {
this.mailServerService.messages.updateMessage(wildduckUserId, messageLocation.mailboxId, messageId, {
moveTo: favoriteMailboxId,
}),
);
@@ -590,26 +596,27 @@ export class EmailService {
};
} catch (error) {
this.logger.error(
`Failed to move message ${messageId} to favorite for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`,
`Failed to move message ${messageId} to favorite for user ${wildduckUserId}: ${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}`);
//==============================================
async moveMessageToTrash(wildduckUserId: string, messageId: number) {
this.logger.log(`Moving message ${messageId} to trash for user: ${wildduckUserId}`);
try {
const messageLocation = await this.findMessageMailbox(userId, messageId);
const messageLocation = await this.findMessageMailbox(wildduckUserId, messageId);
if (!messageLocation) throw new BadRequestException(EmailMessage.MESSAGE_NOT_FOUND);
const trashMailboxId = await this.mailboxResolverService.getTrashMailboxId(userId);
const trashMailboxId = await this.mailboxResolverService.getTrashMailboxId(wildduckUserId);
this.logger.log(`Moving message ${messageId} from ${messageLocation.mailboxName} to ${MailboxEnum.TRASH} for user: ${userId}`);
this.logger.log(`Moving message ${messageId} from ${messageLocation.mailboxName} to ${MailboxEnum.TRASH} for user: ${wildduckUserId}`);
const result = await firstValueFrom(
this.mailServerService.messages.updateMessage(userId, messageLocation.mailboxId, messageId, {
this.mailServerService.messages.updateMessage(wildduckUserId, messageLocation.mailboxId, messageId, {
moveTo: trashMailboxId,
}),
);
@@ -623,22 +630,22 @@ export class EmailService {
};
} catch (error) {
this.logger.error(
`Failed to move message ${messageId} to trash for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`,
`Failed to move message ${messageId} to trash for user ${wildduckUserId}: ${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}`);
async moveMessageToInboxFromArchive(wildduckUserId: string, messageId: number) {
this.logger.log(`Moving message ${messageId} from archive to inbox for user: ${wildduckUserId}`);
try {
const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(userId);
const archiveMailboxId = await this.mailboxResolverService.getArchiveMailboxId(userId);
const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(wildduckUserId);
const archiveMailboxId = await this.mailboxResolverService.getArchiveMailboxId(wildduckUserId);
const result = await firstValueFrom(
this.mailServerService.messages.updateMessage(userId, archiveMailboxId, messageId, {
this.mailServerService.messages.updateMessage(wildduckUserId, archiveMailboxId, messageId, {
moveTo: inboxMailboxId,
}),
);
@@ -652,22 +659,22 @@ export class EmailService {
};
} catch (error) {
this.logger.error(
`Failed to move message ${messageId} from archive to inbox for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`,
`Failed to move message ${messageId} from archive to inbox for user ${wildduckUserId}: ${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}`);
async moveMessageToInboxFromFavorite(wildduckUserId: string, messageId: number) {
this.logger.log(`Moving message ${messageId} from favorite to inbox for user: ${wildduckUserId}`);
try {
const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(userId);
const favoriteMailboxId = await this.mailboxResolverService.getFavoriteMailboxId(userId);
const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(wildduckUserId);
const favoriteMailboxId = await this.mailboxResolverService.getFavoriteMailboxId(wildduckUserId);
const result = await firstValueFrom(
this.mailServerService.messages.updateMessage(userId, favoriteMailboxId, messageId, {
this.mailServerService.messages.updateMessage(wildduckUserId, favoriteMailboxId, messageId, {
moveTo: inboxMailboxId,
}),
);
@@ -681,22 +688,22 @@ export class EmailService {
};
} catch (error) {
this.logger.error(
`Failed to move message ${messageId} from favorite to inbox for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`,
`Failed to move message ${messageId} from favorite to inbox for user ${wildduckUserId}: ${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}`);
async moveMessageToInboxFromTrash(wildduckUserId: string, messageId: number) {
this.logger.log(`Moving message ${messageId} from trash to inbox for user: ${wildduckUserId}`);
try {
const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(userId);
const trashMailboxId = await this.mailboxResolverService.getTrashMailboxId(userId);
const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(wildduckUserId);
const trashMailboxId = await this.mailboxResolverService.getTrashMailboxId(wildduckUserId);
const result = await firstValueFrom(
this.mailServerService.messages.updateMessage(userId, trashMailboxId, messageId, {
this.mailServerService.messages.updateMessage(wildduckUserId, trashMailboxId, messageId, {
moveTo: inboxMailboxId,
}),
);
@@ -710,33 +717,44 @@ export class EmailService {
};
} catch (error) {
this.logger.error(
`Failed to move message ${messageId} from trash to inbox for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`,
`Failed to move message ${messageId} from trash to inbox for user ${wildduckUserId}: ${error instanceof Error ? error.message : "Unknown error"}`,
);
throw error;
}
}
//==============================================
async moveMessageToJunk(userId: string, messageId: number) {
this.logger.log(`Moving message ${messageId} to junk for user: ${userId}`);
async moveMessageToJunk(wildduckUserId: string, messageId: number) {
this.logger.log(`Moving message ${messageId} to junk for user: ${wildduckUserId}`);
try {
const messageLocation = await this.findMessageMailbox(userId, messageId);
const messageLocation = await this.findMessageMailbox(wildduckUserId, messageId);
if (!messageLocation) throw new BadRequestException(EmailMessage.MESSAGE_NOT_FOUND);
const junkMailboxId = await this.mailboxResolverService.getJunkMailboxId(userId);
const junkMailboxId = await this.mailboxResolverService.getJunkMailboxId(wildduckUserId);
this.logger.log(`Moving message ${messageId} from ${messageLocation.mailboxName} to ${MailboxEnum.Junk} for user: ${userId}`);
this.logger.log(`Moving message ${messageId} from ${messageLocation.mailboxName} to ${MailboxEnum.Junk} for user: ${wildduckUserId}`);
const result = await firstValueFrom(
this.mailServerService.messages.updateMessage(userId, messageLocation.mailboxId, messageId, {
this.mailServerService.messages.updateMessage(wildduckUserId, messageLocation.mailboxId, messageId, {
moveTo: junkMailboxId,
}),
);
this.logger.log(`Message ${messageId} moved to junk successfully from ${messageLocation.mailboxName}`);
const domainTag = await this.getUserBusinessId(wildduckUserId);
const spamResult = await this.emailSpamService.markAsSpam({
messageId,
wildduckUserId,
manageDomainList: true,
domainTag,
});
this.logger.log(`Automatic domain management result for message ${messageId}: domainBlocked=${spamResult.domainBlocked}`);
return {
success: true,
message: EmailMessage.MESSAGE_MOVED_TO_JUNK_SUCCESSFULLY,
@@ -744,7 +762,47 @@ export class EmailService {
};
} catch (error) {
this.logger.error(
`Failed to move message ${messageId} to junk for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`,
`Failed to move message ${messageId} to junk for user ${wildduckUserId}: ${error instanceof Error ? error.message : "Unknown error"}`,
);
throw error;
}
}
//==============================================
async moveMessageToInboxFromJunk(wildduckUserId: string, messageId: number) {
this.logger.log(`Moving message ${messageId} from junk to inbox for user: ${wildduckUserId}`);
try {
const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(wildduckUserId);
const junkMailboxId = await this.mailboxResolverService.getJunkMailboxId(wildduckUserId);
const result = await firstValueFrom(
this.mailServerService.messages.updateMessage(wildduckUserId, junkMailboxId, messageId, {
moveTo: inboxMailboxId,
}),
);
this.logger.log(`Message ${messageId} moved from junk to inbox successfully`);
const domainTag = await this.getUserBusinessId(wildduckUserId);
const notSpamResult = await this.emailSpamService.markAsNotSpam({
messageId,
wildduckUserId,
manageDomainList: true,
domainTag,
});
this.logger.log(`Automatic domain management result for message ${messageId}: domainAllowed=${notSpamResult.domainAllowed}`);
return {
success: true,
message: EmailMessage.MESSAGE_MOVED_TO_INBOX_FROM_JUNK_SUCCESSFULLY,
result,
};
} catch (error) {
this.logger.error(
`Failed to move message ${messageId} from junk to inbox for user ${wildduckUserId}: ${error instanceof Error ? error.message : "Unknown error"}`,
);
throw error;
}
@@ -753,8 +811,8 @@ export class EmailService {
//==============================================
// BULK ACTIONS
//==============================================
async performBulkAction(userId: string, bulkActionDto: BulkActionDto) {
this.logger.log(`Performing bulk action ${bulkActionDto.action} on ${bulkActionDto.messageIds.length} messages for user: ${userId}`);
async performBulkAction(wildduckUserId: string, bulkActionDto: BulkActionDto) {
this.logger.log(`Performing bulk action ${bulkActionDto.action} on ${bulkActionDto.messageIds.length} messages for user: ${wildduckUserId}`);
const results = {
successful: [] as number[],
@@ -766,31 +824,34 @@ export class EmailService {
try {
switch (bulkActionDto.action) {
case BulkActionType.SEEN:
await this.markMessageAsSeen(userId, messageId);
await this.markMessageAsSeen(wildduckUserId, messageId);
break;
case BulkActionType.DELETE:
await this.deleteMessage(userId, messageId);
await this.deleteMessage(wildduckUserId, messageId);
break;
case BulkActionType.ARCHIVE:
await this.moveMessageToArchive(userId, messageId);
await this.moveMessageToArchive(wildduckUserId, messageId);
break;
case BulkActionType.UNARCHIVE:
await this.moveMessageToInboxFromArchive(userId, messageId);
await this.moveMessageToInboxFromArchive(wildduckUserId, messageId);
break;
case BulkActionType.FAVORITE:
await this.moveMessageToFavorite(userId, messageId);
await this.moveMessageToFavorite(wildduckUserId, messageId);
break;
case BulkActionType.UNFAVORITE:
await this.moveMessageToInboxFromFavorite(userId, messageId);
await this.moveMessageToInboxFromFavorite(wildduckUserId, messageId);
break;
case BulkActionType.JUNK:
await this.moveMessageToJunk(userId, messageId);
await this.moveMessageToJunk(wildduckUserId, messageId);
break;
case BulkActionType.NOTJUNK:
await this.moveMessageToInboxFromJunk(wildduckUserId, messageId);
break;
case BulkActionType.TRASH:
await this.moveMessageToTrash(userId, messageId);
await this.moveMessageToTrash(wildduckUserId, messageId);
break;
case BulkActionType.RESTORE:
await this.moveMessageToInboxFromTrash(userId, messageId);
await this.moveMessageToInboxFromTrash(wildduckUserId, messageId);
break;
default:
throw new BadRequestException(`Unknown action: ${bulkActionDto.action}`);
@@ -832,9 +893,6 @@ export class EmailService {
//########################################################
/**
* Transform pagination query for WildDuck API compatibility
*/
private transformPaginationQuery(query: MessageListQueryDto): Record<string, unknown> {
const transformedQuery: Record<string, any> = { ...query };
@@ -898,6 +956,14 @@ export class EmailService {
}
}
//==============================================
private async getUserBusinessId(wildduckUserId: string) {
const user = await this.userRepository.findOne({ wildduckUserId, deletedAt: null }, { populate: ["business"] });
return user?.business?.id || "default";
}
//==============================================
//########################################################
private generateHeadersForEmailType(user: User) {
// const emailType = sendEmailDto.emailType || EmailType.GENERAL;
@@ -0,0 +1,38 @@
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
import { IsNotEmpty, IsOptional, IsString } from "class-validator";
export class CreateDomainAccessDto {
@ApiProperty({
description: "Domain name to add to the list",
example: "example.com",
})
@IsNotEmpty()
@IsString()
domain: string;
@ApiPropertyOptional({
description: "Optional description for this domain entry",
example: "Trusted business partner domain",
})
@IsOptional()
@IsString()
description?: string;
}
export class ListDomainAccessQueryDto {
@ApiPropertyOptional({
description: "Number of entries to return",
example: 20,
default: 20,
})
@IsOptional()
limit?: number;
@ApiPropertyOptional({
description: "Page number for pagination",
example: 1,
default: 1,
})
@IsOptional()
page?: number;
}
-30
View File
@@ -1,30 +0,0 @@
// User DTOs
export * from "./create-user.dto";
export * from "./update-user.dto";
// Message DTOs
export * from "./message.dto";
// Mailbox DTOs
export * from "./mailbox.dto";
// Address DTOs
export * from "./address.dto";
// Authentication DTOs
export * from "./authenticate.dto";
// Filter DTOs
export * from "./filter.dto";
// Submission DTOs
export * from "./submission.dto";
// DKIM DTOs
export * from "./dkim.dto";
// Webhook DTOs
export * from "./webhook.dto";
// Two-Factor Authentication DTOs
export * from "./two-factor.dto";
@@ -0,0 +1,3 @@
export interface AttachmentResponse {
success: File;
}
@@ -0,0 +1,23 @@
import { WildDuckPaginatedResponse, WildDuckSuccessResponse } from "./wildduck-response.interface";
export interface DomainAccessEntry {
id: string;
domain: string;
description?: string;
created: string;
}
export interface DomainAccessCreateResponse extends WildDuckSuccessResponse {
id: string;
}
export interface DomainAccessListResponse extends WildDuckPaginatedResponse {
results: DomainAccessEntry[];
}
export interface DomainAccessDetailsResponse extends WildDuckSuccessResponse, DomainAccessEntry {}
export interface DomainAccessListQueryDto {
limit?: number;
page?: number;
}
@@ -1,3 +1,9 @@
interface IMessage {
id: string;
mailbox: string;
size: number;
}
export interface MessageAttachment {
id: string;
filename: string;
@@ -70,9 +76,7 @@ export interface MessageListResponse {
export interface MessageUploadResponse {
success: true;
id: number;
mailbox: string;
size: number;
message: IMessage;
}
export interface MessageEventsEntry {
@@ -104,7 +108,7 @@ export interface MessageForwardResponse {
export interface MessageSubmitResponse {
success: true;
id: string;
message: IMessage;
queueId: string;
}
@@ -11,6 +11,7 @@ import { WildDuckAuthService } from "./services/wildduck-auth.service";
import { WildDuckAutorepliesService } from "./services/wildduck-autoreplies.service";
import { WildDuckBaseService } from "./services/wildduck-base.service";
import { WildDuckDKIMService } from "./services/wildduck-dkim.service";
import { WildDuckDomainAccessService } from "./services/wildduck-domain-access.service";
import { WildDuckExportService } from "./services/wildduck-export.service";
import { WildDuckFiltersService } from "./services/wildduck-filters.service";
import { WildDuckHealthService } from "./services/wildduck-health.service";
@@ -48,6 +49,7 @@ import { wildduckConfig } from "../../configs/wildduck.config";
WildDuckAutorepliesService,
WildDuckExportService,
WildDuckTwoFactorService,
WildDuckDomainAccessService,
],
exports: [
MailServerService,
@@ -69,6 +71,7 @@ import { wildduckConfig } from "../../configs/wildduck.config";
WildDuckAutorepliesService,
WildDuckExportService,
WildDuckTwoFactorService,
WildDuckDomainAccessService,
],
})
export class MailServerModule {}
@@ -7,6 +7,7 @@ import { WildDuckAuditService } from "./wildduck-audit.service";
import { WildDuckAuthService } from "./wildduck-auth.service";
import { WildDuckAutorepliesService } from "./wildduck-autoreplies.service";
import { WildDuckDKIMService } from "./wildduck-dkim.service";
import { WildDuckDomainAccessService } from "./wildduck-domain-access.service";
import { WildDuckExportService } from "./wildduck-export.service";
import { WildDuckFiltersService } from "./wildduck-filters.service";
import { WildDuckHealthService } from "./wildduck-health.service";
@@ -42,6 +43,7 @@ export class MailServerService {
private readonly autorepliesService: WildDuckAutorepliesService,
private readonly exportService: WildDuckExportService,
private readonly twoFactorService: WildDuckTwoFactorService,
private readonly domainAccessService: WildDuckDomainAccessService,
) {}
getMailServerInfo() {
@@ -133,4 +135,8 @@ export class MailServerService {
get twoFactor() {
return this.twoFactorService;
}
get domainAccess() {
return this.domainAccessService;
}
}
@@ -0,0 +1,52 @@
import { Injectable } from "@nestjs/common";
import { Observable } from "rxjs";
import { WildDuckBaseService } from "./wildduck-base.service";
import { CreateDomainAccessDto, ListDomainAccessQueryDto } from "../DTO/domain-access.dto";
import { DomainAccessCreateResponse, DomainAccessListResponse } from "../interfaces/domain-access-response.interface";
import { WildDuckSuccessResponse } from "../interfaces/wildduck-response.interface";
@Injectable()
export class WildDuckDomainAccessService extends WildDuckBaseService {
/**
* List allowlisted domains for a tag
*/
listAllowedDomains(tag: string, query?: ListDomainAccessQueryDto): Observable<DomainAccessListResponse> {
return this.get<DomainAccessListResponse>(`/domainaccess/${tag}/allow`, query);
}
/**
* Add domain to allowlist
*/
addAllowedDomain(tag: string, data: CreateDomainAccessDto): Observable<DomainAccessCreateResponse> {
return this.post<DomainAccessCreateResponse>(`/domainaccess/${tag}/allow`, data);
}
/**
* Remove domain from allowlist
*/
removeAllowedDomain(tag: string, domain: string): Observable<WildDuckSuccessResponse> {
return this.delete<WildDuckSuccessResponse>(`/domainaccess/${tag}/allow/${domain}`);
}
/**
* List blocked domains for a tag
*/
listBlockedDomains(tag: string, query?: ListDomainAccessQueryDto): Observable<DomainAccessListResponse> {
return this.get<DomainAccessListResponse>(`/domainaccess/${tag}/block`, query);
}
/**
* Add domain to blocklist
*/
addBlockedDomain(tag: string, data: CreateDomainAccessDto): Observable<DomainAccessCreateResponse> {
return this.post<DomainAccessCreateResponse>(`/domainaccess/${tag}/block`, data);
}
/**
* Remove domain from blocklist
*/
removeBlockedDomain(tag: string, domain: string): Observable<WildDuckSuccessResponse> {
return this.delete<WildDuckSuccessResponse>(`/domainaccess/${tag}/block/${domain}`);
}
}
@@ -3,6 +3,7 @@ import { Observable } from "rxjs";
import { WildDuckBaseService } from "./wildduck-base.service";
import { ListMessagesQueryDto, SearchMessagesQueryDto, UpdateMessageDto, UploadMessageDto } from "../DTO/message.dto";
import { AttachmentResponse } from "../interfaces/attachment.interface";
import {
MessageDeleteAllResponse,
MessageDetails,
@@ -72,9 +73,9 @@ export class WildDuckMessagesService extends WildDuckBaseService {
/**
* Get message attachment
*/
getMessageAttachment(userId: string, mailboxId: string, messageId: number, attachmentId: string): Observable<any> {
getMessageAttachment(userId: string, mailboxId: string, messageId: number, attachmentId: string): Observable<AttachmentResponse> {
// This returns binary content
return this.get<any>(`/users/${userId}/mailboxes/${mailboxId}/messages/${messageId}/attachments/${attachmentId}`);
return this.get<AttachmentResponse>(`/users/${userId}/mailboxes/${mailboxId}/messages/${messageId}/attachments/${attachmentId}`);
}
/**
+1 -1
View File
@@ -2,11 +2,11 @@ import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from "@nestj
import { ApiOperation, ApiParam, ApiQuery, ApiResponse } from "@nestjs/swagger";
import { MailboxQueryDto } from "./DTO/mailbox.dto";
import { CreateMailboxDto, UpdateMailboxDto } from "./DTO/mailbox.dto";
import { MailboxService } from "./services/mailbox.service";
import { AdminRoute } from "../../common/decorators/admin.decorator";
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
import { UserDec } from "../../common/decorators/user.decorator";
import { CreateMailboxDto, UpdateMailboxDto } from "../mail-server/DTO";
@AuthGuards()
@Controller("mailbox")
@@ -1,9 +1,9 @@
import { Injectable, Logger, NotFoundException } from "@nestjs/common";
import { Observable, catchError, map, throwError } from "rxjs";
import { CreateMailboxDto, UpdateMailboxDto } from "../../mail-server/DTO";
import { MailServerService } from "../../mail-server/services/mail-server.service";
import { MailboxQueryDto } from "../DTO/mailbox.dto";
import { CreateMailboxDto, UpdateMailboxDto } from "../DTO/mailbox.dto";
@Injectable()
export class MailboxService {
+1 -1
View File
@@ -8,7 +8,7 @@ import { Business } from "../../businesses/entities/business.entity";
import { DomainStatus } from "../../domains/enums/domain-status.enum";
import { DomainAutomationService } from "../../domains/services/domain-automation.service";
import { DomainsService } from "../../domains/services/domains.service";
import { UpdateUserDto } from "../../mail-server/DTO";
import { UpdateUserDto } from "../../mail-server/DTO/update-user.dto";
import { MailServerService } from "../../mail-server/services/mail-server.service";
import { QuotaSyncService } from "../../quota-sync/services/quota-sync.service";
import { UserSettingsService } from "../../settings/services/user-settings.service";