chore: change all to have mailbox id in query
This commit is contained in:
@@ -147,8 +147,8 @@ export const enum EmailMessage {
|
||||
MESSAGE_DELETED_SUCCESSFULLY = "ایمیل با موفقیت حذف شد",
|
||||
USER_ID_REQUIRED = "شناسه کاربری مورد نیاز است",
|
||||
USER_ID_MUST_BE_STRING = "شناسه کاربری باید یک رشته باشد",
|
||||
MAILBOX_ID_REQUIRED = "شناسه مستندک ایمیل مورد نیاز است",
|
||||
MAILBOX_ID_MUST_BE_STRING = "شناسه مستندک ایمیل باید یک رشته باشد",
|
||||
MAILBOX_ID_REQUIRED = "شناسه میل باکس ایمیل مورد نیاز است",
|
||||
MAILBOX_ID_MUST_BE_STRING = "شناسه میل باکس ایمیل باید یک رشته باشد",
|
||||
QUEUE_ID_REQUIRED = "شناسه پیشنویس ایمیل مورد نیاز است",
|
||||
QUEUE_ID_MUST_BE_STRING = "شناسه پیشنویس ایمیل باید یک رشته باشد",
|
||||
MESSAGE_ID_MUST_BE_NUMBER = "شناسه ایمیل باید یک عدد باشد",
|
||||
@@ -223,6 +223,7 @@ export const enum EmailMessage {
|
||||
PRIORITY_MUST_BE_ONE_OF_HIGH_NORMAL_LOW = "اولویت باید یکی از مقادیر high, normal, low باشد",
|
||||
SUBJECT_REQUIRED = "موضوع ایمیل مورد نیاز است",
|
||||
ALL_MESSAGES_DELETED_SUCCESSFULLY = "همه پیام ها با موفقیت حذف شدند",
|
||||
MAILBOX_ID_MUST_BE_MONGO_ID = "شناسه میل باکس ایمیل باید یک آی دی معتبر باشد",
|
||||
}
|
||||
|
||||
export const enum WebSocketMessage {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Transform } from "class-transformer";
|
||||
import { IsNotEmpty, IsNumber, IsNumberString, IsString } from "class-validator";
|
||||
import { IsMongoId, IsNotEmpty, IsNumber, IsNumberString, IsString } from "class-validator";
|
||||
|
||||
import { EmailMessage } from "../../../common/enums/message.enum";
|
||||
|
||||
@@ -11,11 +11,12 @@ export class UserIdParamDto {
|
||||
userId: string;
|
||||
}
|
||||
|
||||
export class MailboxIdParamDto {
|
||||
@ApiProperty({ description: "Mailbox ID", example: "507f1f77bcf86cd799439012" })
|
||||
@IsString({ message: EmailMessage.MAILBOX_ID_MUST_BE_STRING })
|
||||
export class MailboxIdQueryDto {
|
||||
@IsNotEmpty({ message: EmailMessage.MAILBOX_ID_REQUIRED })
|
||||
mailboxId: string;
|
||||
@IsString({ message: EmailMessage.MAILBOX_ID_MUST_BE_STRING })
|
||||
@IsMongoId({ message: EmailMessage.MAILBOX_ID_MUST_BE_MONGO_ID })
|
||||
@ApiProperty({ description: "Mailbox ID", example: "507f1f77bcf86cd799439012" })
|
||||
mailbox: string;
|
||||
}
|
||||
|
||||
export class MessageIdParamDto {
|
||||
@@ -32,7 +33,7 @@ export class QueueIdParamDto {
|
||||
queueId: string;
|
||||
}
|
||||
|
||||
export class UserMailboxParamDto {
|
||||
export class MailboxParamDto {
|
||||
@ApiProperty({ description: "Mailbox ID", example: "507f1f77bcf86cd799439012" })
|
||||
@IsString({ message: EmailMessage.MAILBOX_ID_MUST_BE_STRING })
|
||||
@IsNotEmpty({ message: EmailMessage.MAILBOX_ID_REQUIRED })
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from "@nestj
|
||||
import { ApiOperation, ApiResponse } from "@nestjs/swagger";
|
||||
|
||||
import { BulkActionDto } from "./DTO/bulk-actions.dto";
|
||||
import { MessageParamDto, QueueIdParamDto } from "./DTO/email-params.dto";
|
||||
import { MailboxIdQueryDto, MessageParamDto, QueueIdParamDto } from "./DTO/email-params.dto";
|
||||
import { MessageListQueryDto } from "./DTO/email-query.dto";
|
||||
import { SearchMessagesQueryDto } from "./DTO/email-query.dto";
|
||||
import { SendEmailDto, UpdateDraftDto } from "./DTO/send-email.dto";
|
||||
@@ -40,16 +40,16 @@ export class EmailController {
|
||||
@ApiOperation({ summary: "Get a specific message" })
|
||||
@ApiResponse({ status: 200, description: "Message details" })
|
||||
@ApiResponse({ status: 404, description: "Message not found" })
|
||||
getMessage(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto) {
|
||||
return this.emailService.getMessage(userId, params.messageId);
|
||||
getMessage(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
||||
return this.emailService.getMessage(userId, params.messageId, query.mailbox);
|
||||
}
|
||||
|
||||
@Delete("messages/:messageId")
|
||||
@ApiOperation({ summary: "Delete a message" })
|
||||
@ApiResponse({ status: 200, description: "Message deleted successfully" })
|
||||
@ApiResponse({ status: 404, description: "Message not found" })
|
||||
deleteMessage(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto) {
|
||||
return this.emailService.deleteMessage(userId, params.messageId);
|
||||
deleteMessage(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
||||
return this.emailService.deleteMessage(userId, params.messageId, query.mailbox);
|
||||
}
|
||||
|
||||
@Get("search")
|
||||
@@ -63,8 +63,8 @@ export class EmailController {
|
||||
@ApiOperation({ summary: "Mark a message as seen" })
|
||||
@ApiResponse({ status: 200, description: "Message marked as seen successfully" })
|
||||
@ApiResponse({ status: 404, description: "Message not found" })
|
||||
markMessageAsSeen(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto) {
|
||||
return this.emailService.markMessageAsSeen(userId, Number(params.messageId));
|
||||
markMessageAsSeen(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
||||
return this.emailService.markMessageAsSeen(userId, Number(params.messageId), query.mailbox);
|
||||
}
|
||||
|
||||
@Get("messages/inbox")
|
||||
@@ -156,8 +156,8 @@ export class EmailController {
|
||||
@ApiResponse({ status: 200, description: "Message moved to archive successfully" })
|
||||
@ApiResponse({ status: 404, description: "Message not found" })
|
||||
@ApiResponse({ status: 403, description: "Not authorized to move message" })
|
||||
moveMessageToArchive(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto) {
|
||||
return this.emailService.moveMessageToArchive(userId, Number(params.messageId));
|
||||
moveMessageToArchive(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
||||
return this.emailService.moveMessageToArchive(userId, Number(params.messageId), query.mailbox);
|
||||
}
|
||||
|
||||
@Patch("messages/:messageId/favorite")
|
||||
@@ -165,8 +165,8 @@ export class EmailController {
|
||||
@ApiResponse({ status: 200, description: "Message moved to favorite successfully" })
|
||||
@ApiResponse({ status: 404, description: "Message not found" })
|
||||
@ApiResponse({ status: 403, description: "Not authorized to move message" })
|
||||
moveMessageToFavorite(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto) {
|
||||
return this.emailService.moveMessageToFavorite(userId, Number(params.messageId));
|
||||
moveMessageToFavorite(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
||||
return this.emailService.moveMessageToFavorite(userId, Number(params.messageId), query.mailbox);
|
||||
}
|
||||
|
||||
@Patch("messages/:messageId/trash")
|
||||
@@ -174,8 +174,8 @@ export class EmailController {
|
||||
@ApiResponse({ status: 200, description: "Message moved to trash successfully" })
|
||||
@ApiResponse({ status: 404, description: "Message not found" })
|
||||
@ApiResponse({ status: 403, description: "Not authorized to move message" })
|
||||
moveMessageToTrash(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto) {
|
||||
return this.emailService.moveMessageToTrash(userId, Number(params.messageId));
|
||||
moveMessageToTrash(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
||||
return this.emailService.moveMessageToTrash(userId, Number(params.messageId), query.mailbox);
|
||||
}
|
||||
|
||||
@Patch("messages/:messageId/unarchive")
|
||||
@@ -183,8 +183,8 @@ export class EmailController {
|
||||
@ApiResponse({ status: 200, description: "Message moved from archive to inbox successfully" })
|
||||
@ApiResponse({ status: 404, description: "Message not found in archive" })
|
||||
@ApiResponse({ status: 403, description: "Not authorized to move message" })
|
||||
moveMessageToInboxFromArchive(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto) {
|
||||
return this.emailService.moveMessageToInboxFromArchive(userId, Number(params.messageId));
|
||||
moveMessageToInboxFromArchive(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
||||
return this.emailService.moveMessageToInboxFromArchive(userId, Number(params.messageId), query.mailbox);
|
||||
}
|
||||
|
||||
@Patch("messages/:messageId/unfavorite")
|
||||
@@ -192,8 +192,8 @@ export class EmailController {
|
||||
@ApiResponse({ status: 200, description: "Message moved from favorite to inbox successfully" })
|
||||
@ApiResponse({ status: 404, description: "Message not found in favorite" })
|
||||
@ApiResponse({ status: 403, description: "Not authorized to move message" })
|
||||
moveMessageToInboxFromFavorite(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto) {
|
||||
return this.emailService.moveMessageToInboxFromFavorite(userId, Number(params.messageId));
|
||||
moveMessageToInboxFromFavorite(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
||||
return this.emailService.moveMessageToInboxFromFavorite(userId, Number(params.messageId), query.mailbox);
|
||||
}
|
||||
|
||||
@Patch("messages/:messageId/restore")
|
||||
@@ -201,8 +201,8 @@ export class EmailController {
|
||||
@ApiResponse({ status: 200, description: "Message restored from trash to inbox successfully" })
|
||||
@ApiResponse({ status: 404, description: "Message not found in trash" })
|
||||
@ApiResponse({ status: 403, description: "Not authorized to restore message" })
|
||||
moveMessageToInboxFromTrash(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto) {
|
||||
return this.emailService.moveMessageToInboxFromTrash(userId, Number(params.messageId));
|
||||
moveMessageToInboxFromTrash(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
||||
return this.emailService.moveMessageToInboxFromTrash(userId, Number(params.messageId), query.mailbox);
|
||||
}
|
||||
|
||||
@Patch("messages/:messageId/junk")
|
||||
@@ -210,8 +210,8 @@ export class EmailController {
|
||||
@ApiResponse({ status: 200, description: "Message moved to junk successfully" })
|
||||
@ApiResponse({ status: 404, description: "Message not found" })
|
||||
@ApiResponse({ status: 403, description: "Not authorized to move message" })
|
||||
moveMessageToJunk(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto) {
|
||||
return this.emailService.moveMessageToJunk(userId, Number(params.messageId));
|
||||
moveMessageToJunk(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
||||
return this.emailService.moveMessageToJunk(userId, Number(params.messageId), query.mailbox);
|
||||
}
|
||||
|
||||
@Patch("messages/:messageId/not-junk")
|
||||
@@ -219,8 +219,8 @@ export class EmailController {
|
||||
@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));
|
||||
moveMessageToInboxFromJunk(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
||||
return this.emailService.moveMessageToInboxFromJunk(userId, Number(params.messageId), query.mailbox);
|
||||
}
|
||||
|
||||
@Post("messages/bulk-action")
|
||||
@@ -232,8 +232,8 @@ export class EmailController {
|
||||
@ApiResponse({ status: 206, description: "Bulk action partially completed" })
|
||||
@ApiResponse({ status: 400, description: "Invalid bulk action data" })
|
||||
@ApiResponse({ status: 403, description: "Not authorized to perform bulk action" })
|
||||
performBulkAction(@UserDec("wildduckUserId") userId: string, @Body() bulkActionDto: BulkActionDto) {
|
||||
return this.emailService.performBulkAction(userId, bulkActionDto);
|
||||
performBulkAction(@UserDec("wildduckUserId") userId: string, @Body() bulkActionDto: BulkActionDto, @Query() query: MailboxIdQueryDto) {
|
||||
return this.emailService.performBulkAction(userId, bulkActionDto, query.mailbox);
|
||||
}
|
||||
|
||||
// // Domain Access Management Endpoints
|
||||
|
||||
@@ -25,6 +25,13 @@ export const enum EmailHeaderKey {
|
||||
Importance = "Importance",
|
||||
XCampaignID = "X-Campaign-ID",
|
||||
XMarketingEmail = "X-Marketing-Email",
|
||||
// Image-related anti-spam headers
|
||||
XImageSource = "X-Image-Source",
|
||||
XContentSecurity = "X-Content-Security",
|
||||
XImageValidation = "X-Image-Validation",
|
||||
XOriginVerified = "X-Origin-Verified",
|
||||
XImageCount = "X-Image-Count",
|
||||
XTrustedSources = "X-Trusted-Sources",
|
||||
}
|
||||
|
||||
export enum Priority {
|
||||
|
||||
@@ -104,7 +104,6 @@ export class EmailGateway implements OnGatewayInit, OnGatewayConnection, OnGatew
|
||||
});
|
||||
|
||||
if (userInfo) {
|
||||
// Leave user room
|
||||
const userRoom = WEBSOCKET_ROOMS.USER(userInfo.userId);
|
||||
client.leave(userRoom);
|
||||
|
||||
@@ -137,12 +136,8 @@ export class EmailGateway implements OnGatewayInit, OnGatewayConnection, OnGatew
|
||||
}
|
||||
//************************************************************************ */
|
||||
async notifyEmailSent(userId: string, data: EmailSentPayload) {
|
||||
return await this.sendUserNotification(
|
||||
userId,
|
||||
WEBSOCKET_EVENTS.EMAIL_SENT,
|
||||
data,
|
||||
`Email ${data.messageId} sent to ${data.to.map((t) => t.address).join(", ")}`,
|
||||
);
|
||||
const content = `Email ${data.messageId} sent to ${data.to.map((t) => t.address).join(", ")}`;
|
||||
return await this.sendUserNotification(userId, WEBSOCKET_EVENTS.EMAIL_SENT, data, content);
|
||||
}
|
||||
//************************************************************************ */
|
||||
async notifyUnreadCountUpdate(userId: string, data: UnreadCountPayload) {
|
||||
@@ -193,19 +188,6 @@ export class EmailGateway implements OnGatewayInit, OnGatewayConnection, OnGatew
|
||||
return userIds.filter((userId) => this.isUserConnected(userId));
|
||||
}
|
||||
|
||||
//************************************************************************ */
|
||||
async getBusinessConnectedUsers(businessId: string): Promise<string[]> {
|
||||
try {
|
||||
// Note: Currently returning all connected users since business filtering
|
||||
// requires additional user data lookup. This can be enhanced later.
|
||||
this.logger.warn(`getBusinessConnectedUsers called for business ${businessId} - returning all connected users for now`);
|
||||
return this.getConnectedUserIds();
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to get business connected users for ${businessId}:`, error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PRIVATE HELPER METHODS
|
||||
// ============================================================================
|
||||
|
||||
@@ -9,9 +9,14 @@ export interface IGenerateAntiThreadingHeadersOptions {
|
||||
isTransactional?: boolean;
|
||||
isMarketing?: boolean;
|
||||
priority?: Priority;
|
||||
// Image-related options
|
||||
hasHostedImages?: boolean;
|
||||
imageCount?: number;
|
||||
trustedImageSources?: string[];
|
||||
}
|
||||
|
||||
export interface IGenerateGmailAntiThreadingHeadersOptions extends Pick<IGenerateAntiThreadingHeadersOptions, "businessDomain"> {
|
||||
export interface IGenerateGmailAntiThreadingHeadersOptions {
|
||||
businessDomain: string;
|
||||
conversationBreaker?: string;
|
||||
}
|
||||
|
||||
@@ -25,3 +30,10 @@ export interface IGenerateMarketingHeadersOptions
|
||||
campaignId?: string;
|
||||
unsubscribeUrl: string;
|
||||
}
|
||||
|
||||
export interface IImageDetectionResult {
|
||||
hasHostedImages: boolean;
|
||||
imageCount: number;
|
||||
trustedImageSources: string[];
|
||||
danakCorpImages: string[];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
export interface UserMailboxIds {
|
||||
inbox: string;
|
||||
sent: string;
|
||||
drafts: string;
|
||||
trash: string;
|
||||
junk: string;
|
||||
archive: string;
|
||||
favorite: string;
|
||||
}
|
||||
@@ -6,9 +6,7 @@ import { EMAIL_QUEUE_CONSTANTS } from "../constants/email-events.constant";
|
||||
import { EmailMonitoringJob, EmailSchedulerJob } from "../interfaces/email-events.interface";
|
||||
import { EmailMonitoringService } from "../services/email-monitoring.service";
|
||||
|
||||
@Processor(EMAIL_QUEUE_CONSTANTS.EMAIL_QUEUE, {
|
||||
concurrency: 5,
|
||||
})
|
||||
@Processor(EMAIL_QUEUE_CONSTANTS.EMAIL_QUEUE)
|
||||
export class EmailMonitoringProcessor extends WorkerHost {
|
||||
private readonly logger = new Logger(EmailMonitoringProcessor.name);
|
||||
|
||||
|
||||
@@ -8,27 +8,27 @@ import {
|
||||
IGenerateGmailAntiThreadingHeadersOptions,
|
||||
IGenerateMarketingHeadersOptions,
|
||||
IGenerateTransactionalHeadersOptions,
|
||||
IImageDetectionResult,
|
||||
} from "../interfaces/email-header.interface";
|
||||
|
||||
@Injectable()
|
||||
export class EmailHeadersService {
|
||||
private readonly hostDomain = "danakcorp.com";
|
||||
private readonly hostDomain2 = "storage.danakcorp.com";
|
||||
//****************************************** */
|
||||
generateAntiThreadingHeaders(options: IGenerateAntiThreadingHeadersOptions): EmailHeaderDto[] {
|
||||
const headers: EmailHeaderDto[] = [];
|
||||
|
||||
// Add References header with unique value to break threading
|
||||
headers.push({
|
||||
key: EmailHeaderKey.References,
|
||||
value: `<${uuidv4()}@${options.businessDomain}>`,
|
||||
});
|
||||
|
||||
// Add unique Thread-Index to prevent Outlook threading
|
||||
headers.push({
|
||||
key: EmailHeaderKey.ThreadIndex,
|
||||
value: this.generateThreadIndex(),
|
||||
});
|
||||
|
||||
// Anti-spam headers
|
||||
headers.push({
|
||||
key: EmailHeaderKey.XMailer,
|
||||
value: `Danak Mail Service v1.0`,
|
||||
@@ -44,7 +44,6 @@ export class EmailHeadersService {
|
||||
value: options.priority || Priority.Normal,
|
||||
});
|
||||
|
||||
// Add MIME version
|
||||
headers.push({
|
||||
key: EmailHeaderKey.MIMEVersion,
|
||||
value: "1.0",
|
||||
@@ -60,19 +59,20 @@ export class EmailHeadersService {
|
||||
value: "0.0",
|
||||
});
|
||||
|
||||
// Add feedback loop headers
|
||||
headers.push({
|
||||
key: EmailHeaderKey.XFeedbackID,
|
||||
value: `${uuidv4()}:${options.businessDomain}`,
|
||||
});
|
||||
|
||||
// Add entity reference ID
|
||||
headers.push({
|
||||
key: EmailHeaderKey.XEntityRefID,
|
||||
value: uuidv4(),
|
||||
});
|
||||
|
||||
// Content classification headers
|
||||
if (options.hasHostedImages) {
|
||||
headers.push(...this.generateImageAntiSpamHeaders(options));
|
||||
}
|
||||
|
||||
if (options.isTransactional) {
|
||||
headers.push({
|
||||
key: EmailHeaderKey.XAutoResponseSuppress,
|
||||
@@ -106,7 +106,6 @@ export class EmailHeadersService {
|
||||
}
|
||||
}
|
||||
|
||||
// Add List-ID if provided
|
||||
if (options.listId) {
|
||||
headers.push({
|
||||
key: EmailHeaderKey.ListId,
|
||||
@@ -114,30 +113,107 @@ export class EmailHeadersService {
|
||||
});
|
||||
}
|
||||
|
||||
// Add authentication headers
|
||||
// if (options.businessDomain) {
|
||||
// headers.push({
|
||||
// key: EmailHeaderKey.AuthenticationResults,
|
||||
// value: `${options.businessDomain}; dkim=pass; spf=pass; dmarc=pass`,
|
||||
// });
|
||||
// }
|
||||
return headers;
|
||||
}
|
||||
|
||||
// Generate unique Message-ID to prevent threading
|
||||
// const messageId = options.messageId || this.generateMessageId(options.businessDomain);
|
||||
// headers.push({
|
||||
// key: EmailHeaderKey.MessageId,
|
||||
// value: messageId,
|
||||
// });
|
||||
//****************************************** */
|
||||
generateImageAntiSpamHeaders(options: IGenerateAntiThreadingHeadersOptions): EmailHeaderDto[] {
|
||||
const headers: EmailHeaderDto[] = [];
|
||||
|
||||
// Add custom headers to help with deliverability
|
||||
// headers.push({
|
||||
// key: "X-Originating-IP",
|
||||
// value: "[" + this.getServerIP() + "]",
|
||||
// });
|
||||
if (!options.hasHostedImages) {
|
||||
return headers;
|
||||
}
|
||||
|
||||
headers.push({
|
||||
key: EmailHeaderKey.XImageSource,
|
||||
value: this.hostDomain2,
|
||||
});
|
||||
|
||||
headers.push({
|
||||
key: EmailHeaderKey.XContentSecurity,
|
||||
value: "verified",
|
||||
});
|
||||
|
||||
headers.push({
|
||||
key: EmailHeaderKey.XImageValidation,
|
||||
value: "trusted-source",
|
||||
});
|
||||
|
||||
headers.push({
|
||||
key: EmailHeaderKey.XOriginVerified,
|
||||
value: `${this.hostDomain}-${Date.now()}`,
|
||||
});
|
||||
|
||||
if (options.imageCount && options.imageCount > 0) {
|
||||
headers.push({
|
||||
key: EmailHeaderKey.XImageCount,
|
||||
value: options.imageCount.toString(),
|
||||
});
|
||||
}
|
||||
|
||||
if (options.trustedImageSources && options.trustedImageSources.length > 0) {
|
||||
headers.push({
|
||||
key: EmailHeaderKey.XTrustedSources,
|
||||
value: options.trustedImageSources.join(", "),
|
||||
});
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
//****************************************** */
|
||||
detectImagesInContent(htmlContent: string): IImageDetectionResult {
|
||||
if (!htmlContent) {
|
||||
return {
|
||||
hasHostedImages: false,
|
||||
imageCount: 0,
|
||||
trustedImageSources: [],
|
||||
danakCorpImages: [],
|
||||
};
|
||||
}
|
||||
|
||||
// Regex to find img tags with src attributes
|
||||
const imgRegex = /<img[^>]+src\s*=\s*["']([^"']+)["'][^>]*>/gi;
|
||||
const matches = [...htmlContent.matchAll(imgRegex)];
|
||||
|
||||
const allImages = matches.map((match) => match[1]);
|
||||
const danakCorpImages = allImages.filter((src) => src.includes(this.hostDomain) || src.includes(this.hostDomain2));
|
||||
|
||||
// Also check for CSS background images
|
||||
const cssImageRegex = /background-image\s*:\s*url\(["']?([^"')]+)["']?\)/gi;
|
||||
const cssMatches = [...htmlContent.matchAll(cssImageRegex)];
|
||||
const cssImages = cssMatches.map((match) => match[1]);
|
||||
const danakCorpCssImages = cssImages.filter((src) => src.includes(this.hostDomain) || src.includes(this.hostDomain2));
|
||||
|
||||
const allDanakCorpImages = [...danakCorpImages, ...danakCorpCssImages];
|
||||
const hasHostedImages = allDanakCorpImages.length > 0;
|
||||
|
||||
// Get unique domains from danakcorp images
|
||||
const trustedImageSources = [
|
||||
...new Set(
|
||||
allDanakCorpImages
|
||||
.map((src) => {
|
||||
try {
|
||||
const url = new URL(src.startsWith("//") ? "https:" + src : src);
|
||||
return url.hostname;
|
||||
} catch {
|
||||
// If URL parsing fails, extract domain manually
|
||||
const match = src.match(/(?:https?:\/\/)?(?:www\.)?([^/]+)/);
|
||||
return match ? match[1] : src;
|
||||
}
|
||||
})
|
||||
.filter((domain) => domain.includes(this.hostDomain)),
|
||||
),
|
||||
];
|
||||
|
||||
return {
|
||||
hasHostedImages,
|
||||
imageCount: allDanakCorpImages.length,
|
||||
trustedImageSources,
|
||||
danakCorpImages: allDanakCorpImages,
|
||||
};
|
||||
}
|
||||
|
||||
//****************************************** */
|
||||
generateGmailAntiThreadingHeaders(options: IGenerateGmailAntiThreadingHeadersOptions): EmailHeaderDto[] {
|
||||
const headers: EmailHeaderDto[] = [];
|
||||
@@ -268,20 +344,7 @@ export class EmailHeadersService {
|
||||
}
|
||||
//****************************************** */
|
||||
|
||||
private getPriorityValue(priority: "high" | "normal" | "low"): string {
|
||||
switch (priority) {
|
||||
case "high":
|
||||
return "1";
|
||||
case "low":
|
||||
return "5";
|
||||
default:
|
||||
return "3";
|
||||
}
|
||||
private getPriorityValue(priority: Priority): string {
|
||||
return priority === Priority.High ? "1" : priority === Priority.Low ? "5" : "3";
|
||||
}
|
||||
|
||||
// private getServerIP(): string {
|
||||
// // In a real implementation, you might want to get the actual server IP
|
||||
// // For now, returning a placeholder
|
||||
// return "127.0.0.1";
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -62,7 +62,10 @@ export class EmailService {
|
||||
name: user.displayName,
|
||||
};
|
||||
|
||||
const headers = this.generateHeadersForEmailType(user);
|
||||
const headers = this.generateHeadersForEmailType(user, {
|
||||
html: sendEmailDto.html,
|
||||
text: sendEmailDto.text,
|
||||
});
|
||||
|
||||
sendEmailDto.headers = [...headers];
|
||||
|
||||
@@ -177,21 +180,21 @@ export class EmailService {
|
||||
}
|
||||
|
||||
//########################################################
|
||||
async getMessage(wildduckUserId: string, messageId: number) {
|
||||
async getMessage(wildduckUserId: string, messageId: number, mailboxId: string) {
|
||||
try {
|
||||
const messageLocation = await this.findMessageMailbox(wildduckUserId, messageId);
|
||||
this.logger.log(`Getting message ${messageId} from ${mailboxId} mailbox for user: ${wildduckUserId}`);
|
||||
|
||||
if (!messageLocation) throw new BadRequestException(EmailMessage.MESSAGE_NOT_FOUND);
|
||||
await this.markMessageAsSeen(wildduckUserId, messageId, mailboxId);
|
||||
const message = await firstValueFrom(this.mailServerService.messages.getMessage(wildduckUserId, mailboxId, messageId));
|
||||
|
||||
this.logger.log(`Getting message ${messageId} from ${messageLocation.mailboxName} mailbox for user: ${wildduckUserId}`);
|
||||
|
||||
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: ${wildduckUserId}`);
|
||||
this.logger.log(`Retrieved message ${messageId} from ${mailboxId} for user: ${wildduckUserId}`);
|
||||
const mailboxName = await this.mailboxResolverService.getMailboxName(wildduckUserId, mailboxId);
|
||||
|
||||
return {
|
||||
message,
|
||||
message: {
|
||||
...message,
|
||||
mailboxName,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to get message ${messageId} for user ${wildduckUserId}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
||||
@@ -200,17 +203,13 @@ export class EmailService {
|
||||
}
|
||||
|
||||
//########################################################
|
||||
async deleteMessage(wildduckUserId: string, messageId: number) {
|
||||
async deleteMessage(wildduckUserId: string, messageId: number, mailboxId: string) {
|
||||
try {
|
||||
const messageLocation = await this.findMessageMailbox(wildduckUserId, messageId);
|
||||
this.logger.log(`Deleting message ${messageId} from ${mailboxId} mailbox for user: ${wildduckUserId}`);
|
||||
|
||||
if (!messageLocation) throw new BadRequestException(EmailMessage.MESSAGE_NOT_FOUND);
|
||||
const result = await firstValueFrom(this.mailServerService.messages.deleteMessage(wildduckUserId, mailboxId, messageId));
|
||||
|
||||
this.logger.log(`Deleting message ${messageId} from ${messageLocation.mailboxName} mailbox for user: ${wildduckUserId}`);
|
||||
|
||||
const result = await firstValueFrom(this.mailServerService.messages.deleteMessage(wildduckUserId, messageLocation.mailboxId, messageId));
|
||||
|
||||
this.logger.log(`Message ${messageId} deleted successfully from ${messageLocation.mailboxName}`);
|
||||
this.logger.log(`Message ${messageId} deleted successfully from ${mailboxId}`);
|
||||
|
||||
return {
|
||||
message: EmailMessage.MESSAGE_DELETED_SUCCESSFULLY,
|
||||
@@ -243,24 +242,18 @@ export class EmailService {
|
||||
}
|
||||
|
||||
//==============================================
|
||||
async markMessageAsSeen(wildduckUserId: string, messageId: number) {
|
||||
async markMessageAsSeen(wildduckUserId: string, messageId: number, mailboxId: string) {
|
||||
try {
|
||||
const messageLocation = await this.findMessageMailbox(wildduckUserId, messageId);
|
||||
this.logger.log(`Marking message ${messageId} as seen in ${mailboxId} mailbox for user: ${wildduckUserId}`);
|
||||
|
||||
if (!messageLocation) throw new BadRequestException(EmailMessage.MESSAGE_NOT_FOUND);
|
||||
const { updated } = await firstValueFrom(this.mailServerService.messages.updateMessage(wildduckUserId, mailboxId, messageId, { seen: true }));
|
||||
|
||||
this.logger.log(`Marking message ${messageId} as seen in ${messageLocation.mailboxName} mailbox for user: ${wildduckUserId}`);
|
||||
|
||||
const { updated } = await firstValueFrom(
|
||||
this.mailServerService.messages.updateMessage(wildduckUserId, messageLocation.mailboxId, messageId, { seen: true }),
|
||||
);
|
||||
|
||||
this.logger.log(`Message ${messageId} marked as seen successfully in ${messageLocation.mailboxName}`);
|
||||
this.logger.log(`Message ${messageId} marked as seen successfully in ${mailboxId}`);
|
||||
|
||||
await this.emailNotificationService.notifyEmailRead({
|
||||
userId: wildduckUserId,
|
||||
messageId,
|
||||
mailboxId: messageLocation.mailboxId,
|
||||
mailboxId,
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -468,6 +461,12 @@ export class EmailService {
|
||||
if (processedTemplate.hasTemplate) {
|
||||
this.logger.log(`Draft content processed through template for business: ${user.business.id}`);
|
||||
}
|
||||
|
||||
const headers = this.generateHeadersForEmailType(user, {
|
||||
html: draftData.html,
|
||||
text: draftData.text,
|
||||
});
|
||||
draftData.headers = [...(draftData.headers || []), ...headers];
|
||||
}
|
||||
|
||||
const response = await firstValueFrom(this.mailServerService.submission.submitMessage(wildduckUserId, draftData));
|
||||
@@ -535,25 +534,21 @@ export class EmailService {
|
||||
}
|
||||
|
||||
//==============================================
|
||||
async moveMessageToArchive(wildduckUserId: string, messageId: number) {
|
||||
async moveMessageToArchive(wildduckUserId: string, messageId: number, mailboxId: string) {
|
||||
this.logger.log(`Moving message ${messageId} to archive for user: ${wildduckUserId}`);
|
||||
|
||||
try {
|
||||
const messageLocation = await this.findMessageMailbox(wildduckUserId, messageId);
|
||||
|
||||
if (!messageLocation) throw new BadRequestException(EmailMessage.MESSAGE_NOT_FOUND);
|
||||
|
||||
const archiveMailboxId = await this.mailboxResolverService.getArchiveMailboxId(wildduckUserId);
|
||||
|
||||
this.logger.log(`Moving message ${messageId} from ${messageLocation.mailboxName} to ${MailboxEnum.ARCHIVE} for user: ${wildduckUserId}`);
|
||||
this.logger.log(`Moving message ${messageId} from ${mailboxId} to ${MailboxEnum.ARCHIVE} for user: ${wildduckUserId}`);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
this.mailServerService.messages.updateMessage(wildduckUserId, messageLocation.mailboxId, messageId, {
|
||||
this.mailServerService.messages.updateMessage(wildduckUserId, mailboxId, messageId, {
|
||||
moveTo: archiveMailboxId,
|
||||
}),
|
||||
);
|
||||
|
||||
this.logger.log(`Message ${messageId} moved to archive successfully from ${messageLocation.mailboxName}`);
|
||||
this.logger.log(`Message ${messageId} moved to archive successfully from ${mailboxId}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@@ -569,25 +564,21 @@ export class EmailService {
|
||||
}
|
||||
|
||||
//==============================================
|
||||
async moveMessageToFavorite(wildduckUserId: string, messageId: number) {
|
||||
async moveMessageToFavorite(wildduckUserId: string, messageId: number, mailboxId: string) {
|
||||
this.logger.log(`Moving message ${messageId} to favorite for user: ${wildduckUserId}`);
|
||||
|
||||
try {
|
||||
const messageLocation = await this.findMessageMailbox(wildduckUserId, messageId);
|
||||
|
||||
if (!messageLocation) throw new BadRequestException(EmailMessage.MESSAGE_NOT_FOUND);
|
||||
|
||||
const favoriteMailboxId = await this.mailboxResolverService.getFavoriteMailboxId(wildduckUserId);
|
||||
|
||||
this.logger.log(`Moving message ${messageId} from ${messageLocation.mailboxName} to ${MailboxEnum.FAVORITE} for user: ${wildduckUserId}`);
|
||||
this.logger.log(`Moving message ${messageId} from ${mailboxId} to ${MailboxEnum.FAVORITE} for user: ${wildduckUserId}`);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
this.mailServerService.messages.updateMessage(wildduckUserId, messageLocation.mailboxId, messageId, {
|
||||
this.mailServerService.messages.updateMessage(wildduckUserId, mailboxId, messageId, {
|
||||
moveTo: favoriteMailboxId,
|
||||
}),
|
||||
);
|
||||
|
||||
this.logger.log(`Message ${messageId} moved to favorite successfully from ${messageLocation.mailboxName}`);
|
||||
this.logger.log(`Message ${messageId} moved to favorite successfully from ${mailboxId}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@@ -603,25 +594,21 @@ export class EmailService {
|
||||
}
|
||||
|
||||
//==============================================
|
||||
async moveMessageToTrash(wildduckUserId: string, messageId: number) {
|
||||
async moveMessageToTrash(wildduckUserId: string, messageId: number, mailboxId: string) {
|
||||
this.logger.log(`Moving message ${messageId} to trash for user: ${wildduckUserId}`);
|
||||
|
||||
try {
|
||||
const messageLocation = await this.findMessageMailbox(wildduckUserId, messageId);
|
||||
|
||||
if (!messageLocation) throw new BadRequestException(EmailMessage.MESSAGE_NOT_FOUND);
|
||||
|
||||
const trashMailboxId = await this.mailboxResolverService.getTrashMailboxId(wildduckUserId);
|
||||
|
||||
this.logger.log(`Moving message ${messageId} from ${messageLocation.mailboxName} to ${MailboxEnum.TRASH} for user: ${wildduckUserId}`);
|
||||
this.logger.log(`Moving message ${messageId} from ${mailboxId} to ${MailboxEnum.TRASH} for user: ${wildduckUserId}`);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
this.mailServerService.messages.updateMessage(wildduckUserId, messageLocation.mailboxId, messageId, {
|
||||
this.mailServerService.messages.updateMessage(wildduckUserId, mailboxId, messageId, {
|
||||
moveTo: trashMailboxId,
|
||||
}),
|
||||
);
|
||||
|
||||
this.logger.log(`Message ${messageId} moved to trash successfully from ${messageLocation.mailboxName}`);
|
||||
this.logger.log(`Message ${messageId} moved to trash successfully from ${mailboxId}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@@ -637,15 +624,14 @@ export class EmailService {
|
||||
}
|
||||
|
||||
//==============================================
|
||||
async moveMessageToInboxFromArchive(wildduckUserId: string, messageId: number) {
|
||||
async moveMessageToInboxFromArchive(wildduckUserId: string, messageId: number, mailboxId: string) {
|
||||
this.logger.log(`Moving message ${messageId} from archive to inbox for user: ${wildduckUserId}`);
|
||||
|
||||
try {
|
||||
const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(wildduckUserId);
|
||||
const archiveMailboxId = await this.mailboxResolverService.getArchiveMailboxId(wildduckUserId);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
this.mailServerService.messages.updateMessage(wildduckUserId, archiveMailboxId, messageId, {
|
||||
this.mailServerService.messages.updateMessage(wildduckUserId, mailboxId, messageId, {
|
||||
moveTo: inboxMailboxId,
|
||||
}),
|
||||
);
|
||||
@@ -666,15 +652,14 @@ export class EmailService {
|
||||
}
|
||||
|
||||
//==============================================
|
||||
async moveMessageToInboxFromFavorite(wildduckUserId: string, messageId: number) {
|
||||
async moveMessageToInboxFromFavorite(wildduckUserId: string, messageId: number, mailboxId: string) {
|
||||
this.logger.log(`Moving message ${messageId} from favorite to inbox for user: ${wildduckUserId}`);
|
||||
|
||||
try {
|
||||
const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(wildduckUserId);
|
||||
const favoriteMailboxId = await this.mailboxResolverService.getFavoriteMailboxId(wildduckUserId);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
this.mailServerService.messages.updateMessage(wildduckUserId, favoriteMailboxId, messageId, {
|
||||
this.mailServerService.messages.updateMessage(wildduckUserId, mailboxId, messageId, {
|
||||
moveTo: inboxMailboxId,
|
||||
}),
|
||||
);
|
||||
@@ -695,15 +680,14 @@ export class EmailService {
|
||||
}
|
||||
|
||||
//==============================================
|
||||
async moveMessageToInboxFromTrash(wildduckUserId: string, messageId: number) {
|
||||
async moveMessageToInboxFromTrash(wildduckUserId: string, messageId: number, mailboxId: string) {
|
||||
this.logger.log(`Moving message ${messageId} from trash to inbox for user: ${wildduckUserId}`);
|
||||
|
||||
try {
|
||||
const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(wildduckUserId);
|
||||
const trashMailboxId = await this.mailboxResolverService.getTrashMailboxId(wildduckUserId);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
this.mailServerService.messages.updateMessage(wildduckUserId, trashMailboxId, messageId, {
|
||||
this.mailServerService.messages.updateMessage(wildduckUserId, mailboxId, messageId, {
|
||||
moveTo: inboxMailboxId,
|
||||
}),
|
||||
);
|
||||
@@ -724,25 +708,21 @@ export class EmailService {
|
||||
}
|
||||
|
||||
//==============================================
|
||||
async moveMessageToJunk(wildduckUserId: string, messageId: number) {
|
||||
async moveMessageToJunk(wildduckUserId: string, messageId: number, mailboxId: string) {
|
||||
this.logger.log(`Moving message ${messageId} to junk for user: ${wildduckUserId}`);
|
||||
|
||||
try {
|
||||
const messageLocation = await this.findMessageMailbox(wildduckUserId, messageId);
|
||||
|
||||
if (!messageLocation) throw new BadRequestException(EmailMessage.MESSAGE_NOT_FOUND);
|
||||
|
||||
const junkMailboxId = await this.mailboxResolverService.getJunkMailboxId(wildduckUserId);
|
||||
|
||||
this.logger.log(`Moving message ${messageId} from ${messageLocation.mailboxName} to ${MailboxEnum.Junk} for user: ${wildduckUserId}`);
|
||||
this.logger.log(`Moving message ${messageId} from ${mailboxId} to ${MailboxEnum.Junk} for user: ${wildduckUserId}`);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
this.mailServerService.messages.updateMessage(wildduckUserId, messageLocation.mailboxId, messageId, {
|
||||
this.mailServerService.messages.updateMessage(wildduckUserId, mailboxId, messageId, {
|
||||
moveTo: junkMailboxId,
|
||||
}),
|
||||
);
|
||||
|
||||
this.logger.log(`Message ${messageId} moved to junk successfully from ${messageLocation.mailboxName}`);
|
||||
this.logger.log(`Message ${messageId} moved to junk successfully from ${mailboxId}`);
|
||||
|
||||
const domainTag = await this.getUserBusinessId(wildduckUserId);
|
||||
|
||||
@@ -769,15 +749,14 @@ export class EmailService {
|
||||
}
|
||||
|
||||
//==============================================
|
||||
async moveMessageToInboxFromJunk(wildduckUserId: string, messageId: number) {
|
||||
async moveMessageToInboxFromJunk(wildduckUserId: string, messageId: number, mailboxId: string) {
|
||||
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, {
|
||||
this.mailServerService.messages.updateMessage(wildduckUserId, mailboxId, messageId, {
|
||||
moveTo: inboxMailboxId,
|
||||
}),
|
||||
);
|
||||
@@ -811,7 +790,7 @@ export class EmailService {
|
||||
//==============================================
|
||||
// BULK ACTIONS
|
||||
//==============================================
|
||||
async performBulkAction(wildduckUserId: string, bulkActionDto: BulkActionDto) {
|
||||
async performBulkAction(wildduckUserId: string, bulkActionDto: BulkActionDto, mailboxId: string) {
|
||||
this.logger.log(`Performing bulk action ${bulkActionDto.action} on ${bulkActionDto.messageIds.length} messages for user: ${wildduckUserId}`);
|
||||
|
||||
const results = {
|
||||
@@ -824,34 +803,34 @@ export class EmailService {
|
||||
try {
|
||||
switch (bulkActionDto.action) {
|
||||
case BulkActionType.SEEN:
|
||||
await this.markMessageAsSeen(wildduckUserId, messageId);
|
||||
await this.markMessageAsSeen(wildduckUserId, messageId, mailboxId);
|
||||
break;
|
||||
case BulkActionType.DELETE:
|
||||
await this.deleteMessage(wildduckUserId, messageId);
|
||||
await this.deleteMessage(wildduckUserId, messageId, mailboxId);
|
||||
break;
|
||||
case BulkActionType.ARCHIVE:
|
||||
await this.moveMessageToArchive(wildduckUserId, messageId);
|
||||
await this.moveMessageToArchive(wildduckUserId, messageId, mailboxId);
|
||||
break;
|
||||
case BulkActionType.UNARCHIVE:
|
||||
await this.moveMessageToInboxFromArchive(wildduckUserId, messageId);
|
||||
await this.moveMessageToInboxFromArchive(wildduckUserId, messageId, mailboxId);
|
||||
break;
|
||||
case BulkActionType.FAVORITE:
|
||||
await this.moveMessageToFavorite(wildduckUserId, messageId);
|
||||
await this.moveMessageToFavorite(wildduckUserId, messageId, mailboxId);
|
||||
break;
|
||||
case BulkActionType.UNFAVORITE:
|
||||
await this.moveMessageToInboxFromFavorite(wildduckUserId, messageId);
|
||||
await this.moveMessageToInboxFromFavorite(wildduckUserId, messageId, mailboxId);
|
||||
break;
|
||||
case BulkActionType.JUNK:
|
||||
await this.moveMessageToJunk(wildduckUserId, messageId);
|
||||
await this.moveMessageToJunk(wildduckUserId, messageId, mailboxId);
|
||||
break;
|
||||
case BulkActionType.NOTJUNK:
|
||||
await this.moveMessageToInboxFromJunk(wildduckUserId, messageId);
|
||||
await this.moveMessageToInboxFromJunk(wildduckUserId, messageId, mailboxId);
|
||||
break;
|
||||
case BulkActionType.TRASH:
|
||||
await this.moveMessageToTrash(wildduckUserId, messageId);
|
||||
await this.moveMessageToTrash(wildduckUserId, messageId, mailboxId);
|
||||
break;
|
||||
case BulkActionType.RESTORE:
|
||||
await this.moveMessageToInboxFromTrash(wildduckUserId, messageId);
|
||||
await this.moveMessageToInboxFromTrash(wildduckUserId, messageId, mailboxId);
|
||||
break;
|
||||
default:
|
||||
throw new BadRequestException(`Unknown action: ${bulkActionDto.action}`);
|
||||
@@ -920,42 +899,6 @@ export class EmailService {
|
||||
return transformedQuery;
|
||||
}
|
||||
|
||||
//########################################################
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================
|
||||
private async getUserBusinessId(wildduckUserId: string) {
|
||||
const user = await this.userRepository.findOne({ wildduckUserId, deletedAt: null }, { populate: ["business"] });
|
||||
@@ -964,68 +907,21 @@ export class EmailService {
|
||||
|
||||
//==============================================
|
||||
|
||||
//########################################################
|
||||
private generateHeadersForEmailType(user: User) {
|
||||
// const emailType = sendEmailDto.emailType || EmailType.GENERAL;
|
||||
private generateHeadersForEmailType(user: User, emailContent?: { html?: string; text?: string }) {
|
||||
const businessName = user.business.name;
|
||||
const businessDomain = user.emailAddress.split("@")[1];
|
||||
|
||||
const htmlContent = emailContent?.html || "";
|
||||
const imageDetection = this.emailHeadersService.detectImagesInContent(htmlContent);
|
||||
|
||||
return this.emailHeadersService.generateAntiThreadingHeaders({
|
||||
businessName,
|
||||
businessDomain,
|
||||
isTransactional: true,
|
||||
priority: Priority.Normal,
|
||||
hasHostedImages: imageDetection.hasHostedImages,
|
||||
imageCount: imageDetection.imageCount,
|
||||
trustedImageSources: imageDetection.trustedImageSources,
|
||||
});
|
||||
|
||||
// switch (emailType) {
|
||||
// case EmailType.TRANSACTIONAL:
|
||||
// return this.emailHeadersService.generateTransactionalHeaders({
|
||||
// businessName,
|
||||
// businessDomain,
|
||||
// transactionId: sendEmailDto.transactionId,
|
||||
// category: sendEmailDto.category,
|
||||
// });
|
||||
|
||||
// case EmailType.MARKETING:
|
||||
// if (!sendEmailDto.listId || !sendEmailDto.unsubscribeUrl) {
|
||||
// throw new BadRequestException("Marketing emails require listId and unsubscribeUrl");
|
||||
// }
|
||||
// return this.emailHeadersService.generateMarketingHeaders({
|
||||
// businessName,
|
||||
// businessDomain,
|
||||
// listId: sendEmailDto.listId,
|
||||
// unsubscribeUrl: sendEmailDto.unsubscribeUrl,
|
||||
// campaignId: sendEmailDto.campaignId,
|
||||
// });
|
||||
|
||||
// case EmailType.ANTI_THREADING: {
|
||||
// const antiThreadingHeaders = this.emailHeadersService.generateAntiThreadingHeaders({
|
||||
// businessName,
|
||||
// businessDomain,
|
||||
// isTransactional: true,
|
||||
// priority: sendEmailDto.priority as Priority,
|
||||
// });
|
||||
|
||||
// Add Gmail-specific headers if requested
|
||||
// if (sendEmailDto.forceGmailBreak) {
|
||||
// const gmailHeaders = this.emailHeadersService.generateGmailAntiThreadingHeaders({
|
||||
// businessDomain,
|
||||
// conversationBreaker: sendEmailDto.conversationBreaker,
|
||||
// });
|
||||
// antiThreadingHeaders.push(...gmailHeaders);
|
||||
// }
|
||||
|
||||
// return antiThreadingHeaders;
|
||||
// }
|
||||
|
||||
// case EmailType.GENERAL:
|
||||
// default:
|
||||
// return this.emailHeadersService.generateAntiThreadingHeaders({
|
||||
// businessName,
|
||||
// businessDomain,
|
||||
// isTransactional: true,
|
||||
// priority: Priority.Normal,
|
||||
// });
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,7 @@ import { firstValueFrom } from "rxjs";
|
||||
import { MailboxInfo } from "../../mail-server/interfaces/mailboxes-response.interface";
|
||||
import { MailServerService } from "../../mail-server/services/mail-server.service";
|
||||
import { MailboxEnum } from "../../mailbox/enums/mailbox.enum";
|
||||
|
||||
export interface UserMailboxIds {
|
||||
inbox: string;
|
||||
sent: string;
|
||||
drafts: string;
|
||||
trash: string;
|
||||
junk: string;
|
||||
archive: string;
|
||||
favorite: string;
|
||||
}
|
||||
import { UserMailboxIds } from "../interfaces/user-mailbox.interface";
|
||||
|
||||
@Injectable()
|
||||
export class MailboxResolverService {
|
||||
@@ -70,6 +61,15 @@ export class MailboxResolverService {
|
||||
return mailboxId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mailbox name by ID
|
||||
*/
|
||||
async getMailboxName(wildduckUserId: string, mailboxId: string): Promise<string> {
|
||||
const mailboxes = await firstValueFrom(this.mailServerService.mailboxes.listMailboxes(wildduckUserId));
|
||||
const mailbox = mailboxes.results.find((mb) => mb.id === mailboxId);
|
||||
return mailbox?.name || "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get drafts mailbox ID
|
||||
*/
|
||||
|
||||
@@ -13,7 +13,6 @@ import { BusinessInterceptor } from "../../core/interceptors/business.intercepto
|
||||
|
||||
@Controller("users")
|
||||
@ApiHeader({ name: "x-business-id", description: "Business ID" })
|
||||
@UseInterceptors(BusinessInterceptor)
|
||||
@AuthGuards()
|
||||
export class UsersController {
|
||||
constructor(private readonly usersService: UsersService) {}
|
||||
@@ -26,6 +25,7 @@ export class UsersController {
|
||||
}
|
||||
|
||||
@Post()
|
||||
@UseInterceptors(BusinessInterceptor)
|
||||
@ApiOperation({ summary: "Create a new email user" })
|
||||
@ApiResponse({ status: 201, description: "Email user created successfully" })
|
||||
createEmailUser(@Body() createEmailUserDto: CreateEmailUserDto, @BusinessDec("id") businessId: string) {
|
||||
@@ -33,6 +33,7 @@ export class UsersController {
|
||||
}
|
||||
|
||||
@Get()
|
||||
@UseInterceptors(BusinessInterceptor)
|
||||
@ApiOperation({ summary: "Get email users for business" })
|
||||
@ApiResponse({ status: 200, description: "Email users retrieved successfully" })
|
||||
getEmailUsers(@BusinessDec("id") businessId: string, @Query() queryDto: UserListQueryDto) {
|
||||
@@ -40,6 +41,7 @@ export class UsersController {
|
||||
}
|
||||
|
||||
@Delete(":id")
|
||||
@UseInterceptors(BusinessInterceptor)
|
||||
@ApiOperation({ summary: "Delete email user" })
|
||||
@ApiResponse({ status: 200, description: "Email user deleted successfully" })
|
||||
deleteEmailUser(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) {
|
||||
@@ -47,6 +49,7 @@ export class UsersController {
|
||||
}
|
||||
|
||||
@Patch(":id")
|
||||
@UseInterceptors(BusinessInterceptor)
|
||||
@ApiOperation({ summary: "Update email user" })
|
||||
@ApiResponse({ status: 200, description: "Email user updated successfully" })
|
||||
updateEmailUser(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string, @Body() body: UpdateEmailUserDto) {
|
||||
@@ -54,6 +57,7 @@ export class UsersController {
|
||||
}
|
||||
|
||||
@Patch(":id/quota")
|
||||
@UseInterceptors(BusinessInterceptor)
|
||||
@ApiOperation({ summary: "Update email user quota" })
|
||||
@ApiResponse({ status: 200, description: "Email user quota updated successfully" })
|
||||
updateEmailUserQuota(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string, @Body() body: { quota: number }) {
|
||||
@@ -61,6 +65,7 @@ export class UsersController {
|
||||
}
|
||||
|
||||
@Patch(":id/toggle-status")
|
||||
@UseInterceptors(BusinessInterceptor)
|
||||
@ApiOperation({ summary: "Update email user status" })
|
||||
@ApiResponse({ status: 200, description: "Email user status updated successfully" })
|
||||
updateEmailUserStatus(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) {
|
||||
|
||||
Reference in New Issue
Block a user