From c427a8fff27b4020490a69886ecc1134f9459f0a Mon Sep 17 00:00:00 2001 From: mahyargdz Date: Wed, 16 Jul 2025 09:14:37 +0330 Subject: [PATCH] chore: change email header --- src/modules/email/DTO/send-email.dto.ts | 16 ++-- src/modules/email/email.controller.ts | 10 ++- .../email/services/email-headers.service.ts | 82 +++++++++---------- src/modules/email/services/email.service.ts | 19 ++--- 4 files changed, 65 insertions(+), 62 deletions(-) diff --git a/src/modules/email/DTO/send-email.dto.ts b/src/modules/email/DTO/send-email.dto.ts index 77654fc..70c56e8 100644 --- a/src/modules/email/DTO/send-email.dto.ts +++ b/src/modules/email/DTO/send-email.dto.ts @@ -141,14 +141,14 @@ export class SendEmailDto { @ApiPropertyOptional({ description: "Addresses for the Bcc: header (optional)", type: [EmailRecipientDto] }) bcc?: EmailRecipientDto[]; - @IsOptional() - @IsArray({ message: EmailMessage.HEADERS_MUST_BE_ARRAY }) - @ValidateNested({ each: true }) - @Type(() => EmailHeaderDto) - @ApiPropertyOptional({ - description: "Custom headers for the message. If reference message is set then In-Reply-To and References headers are set automatically", - type: [EmailHeaderDto], - }) + // @IsOptional() + // @IsArray({ message: EmailMessage.HEADERS_MUST_BE_ARRAY }) + // @ValidateNested({ each: true }) + // @Type(() => EmailHeaderDto) + // @ApiPropertyOptional({ + // description: "Custom headers for the message. If reference message is set then In-Reply-To and References headers are set automatically", + // type: [EmailHeaderDto], + // }) headers?: EmailHeaderDto[]; @IsOptional() diff --git a/src/modules/email/email.controller.ts b/src/modules/email/email.controller.ts index 0ef5535..5291d31 100644 --- a/src/modules/email/email.controller.ts +++ b/src/modules/email/email.controller.ts @@ -8,6 +8,7 @@ import { SearchMessagesQueryDto } from "./DTO/email-query.dto"; import { SendEmailDto, UpdateDraftDto } from "./DTO/send-email.dto"; import { EmailService } from "./services/email.service"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; +import { BusinessDec } from "../../common/decorators/business.decorator"; import { UserDec } from "../../common/decorators/user.decorator"; @AuthGuards() @@ -24,8 +25,13 @@ export class EmailController { @ApiResponse({ status: 201, description: "Email sent successfully" }) @ApiResponse({ status: 400, description: "Invalid email data or missing required fields for email type" }) @ApiResponse({ status: 403, description: "Not authorized to send email" }) - sendEmail(@UserDec("wildduckUserId") userEmailId: string, @Body() sendEmailDto: SendEmailDto) { - return this.emailService.sendEmail(userEmailId, sendEmailDto); + sendEmail( + @UserDec("wildduckUserId") userEmailId: string, + @BusinessDec("id") businessId: string, + @UserDec("id") userId: string, + @Body() sendEmailDto: SendEmailDto, + ) { + return this.emailService.sendEmail(userEmailId, businessId, userId, sendEmailDto); } @Get("status/:queueId") diff --git a/src/modules/email/services/email-headers.service.ts b/src/modules/email/services/email-headers.service.ts index 65353b3..63f5bbd 100644 --- a/src/modules/email/services/email-headers.service.ts +++ b/src/modules/email/services/email-headers.service.ts @@ -2,7 +2,7 @@ import { Injectable } from "@nestjs/common"; import { v4 as uuidv4 } from "uuid"; import { EmailHeaderDto } from "../DTO/send-email.dto"; -import { EmailHeaderKey } from "../enums/email-header.enum"; +import { EmailHeaderKey, Priority } from "../enums/email-header.enum"; import { IGenerateAntiThreadingHeadersOptions, IGenerateGmailAntiThreadingHeadersOptions, @@ -16,17 +16,10 @@ export class EmailHeadersService { generateAntiThreadingHeaders(options: IGenerateAntiThreadingHeadersOptions): EmailHeaderDto[] { const headers: EmailHeaderDto[] = []; - // Generate unique Message-ID to prevent threading - const messageId = options.messageId || this.generateMessageId(options.businessDomain); - headers.push({ - key: EmailHeaderKey.MessageId, - value: messageId, - }); - // Add References header with unique value to break threading headers.push({ key: EmailHeaderKey.References, - value: `<${uuidv4()}@${options.businessDomain || "localhost"}>`, + value: `<${uuidv4()}@${options.businessDomain}>`, }); // Add unique Thread-Index to prevent Outlook threading @@ -38,17 +31,17 @@ export class EmailHeadersService { // Anti-spam headers headers.push({ key: EmailHeaderKey.XMailer, - value: `${options.businessName || "Email Service"} v1.0`, + value: `Danak Mail Service v1.0`, }); headers.push({ key: EmailHeaderKey.XPriority, - value: this.getPriorityValue(options.priority || "normal"), + value: this.getPriorityValue(options.priority || Priority.Normal), }); headers.push({ key: EmailHeaderKey.XMSMailPriority, - value: options.priority || "Normal", + value: options.priority || Priority.Normal, }); // Add MIME version @@ -57,13 +50,27 @@ export class EmailHeadersService { value: "1.0", }); - // Add authentication headers - if (options.businessDomain) { - headers.push({ - key: EmailHeaderKey.AuthenticationResults, - value: `${options.businessDomain}; dkim=pass; spf=pass; dmarc=pass`, - }); - } + headers.push({ + key: EmailHeaderKey.XSpamStatus, + value: "No", + }); + + headers.push({ + key: EmailHeaderKey.XSpamScore, + 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.isTransactional) { @@ -107,34 +114,27 @@ export class EmailHeadersService { }); } + // Add authentication headers + // if (options.businessDomain) { + // headers.push({ + // key: EmailHeaderKey.AuthenticationResults, + // value: `${options.businessDomain}; dkim=pass; spf=pass; dmarc=pass`, + // }); + // } + + // Generate unique Message-ID to prevent threading + // const messageId = options.messageId || this.generateMessageId(options.businessDomain); + // headers.push({ + // key: EmailHeaderKey.MessageId, + // value: messageId, + // }); + // Add custom headers to help with deliverability // headers.push({ // key: "X-Originating-IP", // value: "[" + this.getServerIP() + "]", // }); - headers.push({ - key: EmailHeaderKey.XSpamStatus, - value: "No", - }); - - headers.push({ - key: EmailHeaderKey.XSpamScore, - value: "0.0", - }); - - // Add feedback loop headers - headers.push({ - key: EmailHeaderKey.XFeedbackID, - value: `${uuidv4()}:${options.businessDomain || "localhost"}`, - }); - - // Add entity reference ID - headers.push({ - key: EmailHeaderKey.XEntityRefID, - value: uuidv4(), - }); - return headers; } diff --git a/src/modules/email/services/email.service.ts b/src/modules/email/services/email.service.ts index 3bbc0c3..41a3c66 100644 --- a/src/modules/email/services/email.service.ts +++ b/src/modules/email/services/email.service.ts @@ -30,18 +30,14 @@ export class EmailService { ) {} //######################################################## - async sendEmail(userEmailId: string, sendEmailDto: SendEmailDto) { + async sendEmail(userEmailId: string, businessId: string, userId: string, sendEmailDto: SendEmailDto) { this.logger.log(`Sending ${EmailType.GENERAL} email from user ${userEmailId} to ${sendEmailDto.to.map((t) => t.address).join(", ")}`); try { - const user = await this.userRepository.findOne({ wildduckUserId: userEmailId, 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}`); + this.logger.log(`Processing email through business template for business: ${businessId}`); // Process email content through business template - const processedTemplate = await this.templateProcessorService.processEmailContent(user.business.id, user.id, { + const processedTemplate = await this.templateProcessorService.processEmailContent(businessId, userId, { text: sendEmailDto.text, html: sendEmailDto.html, subject: sendEmailDto.subject, @@ -57,19 +53,20 @@ export class EmailService { } if (processedTemplate.hasTemplate) { - this.logger.log(`Email content processed through template for business: ${user.business.id}`); + this.logger.log(`Email content processed through template for business: ${businessId}`); } + const user = await this.userRepository.findOne({ wildduckUserId: userEmailId, deletedAt: null, business: { id: businessId } }); + if (!user) throw new BadRequestException(EmailMessage.USER_NOT_FOUND); + sendEmailDto.from = { address: user.emailAddress, name: user.displayName, }; - // Generate headers based on email type const headers = this.generateHeadersForEmailType(user); - // Merge with existing headers if any - sendEmailDto.headers = [...(sendEmailDto.headers || []), ...headers]; + sendEmailDto.headers = [...headers]; const response = await firstValueFrom(this.mailServerService.submission.submitMessage(userEmailId, sendEmailDto));