chore: change email header

This commit is contained in:
mahyargdz
2025-07-16 09:14:37 +03:30
parent 81a3f583a7
commit c427a8fff2
4 changed files with 65 additions and 62 deletions
+8 -8
View File
@@ -141,14 +141,14 @@ export class SendEmailDto {
@ApiPropertyOptional({ description: "Addresses for the Bcc: header (optional)", type: [EmailRecipientDto] }) @ApiPropertyOptional({ description: "Addresses for the Bcc: header (optional)", type: [EmailRecipientDto] })
bcc?: EmailRecipientDto[]; bcc?: EmailRecipientDto[];
@IsOptional() // @IsOptional()
@IsArray({ message: EmailMessage.HEADERS_MUST_BE_ARRAY }) // @IsArray({ message: EmailMessage.HEADERS_MUST_BE_ARRAY })
@ValidateNested({ each: true }) // @ValidateNested({ each: true })
@Type(() => EmailHeaderDto) // @Type(() => EmailHeaderDto)
@ApiPropertyOptional({ // @ApiPropertyOptional({
description: "Custom headers for the message. If reference message is set then In-Reply-To and References headers are set automatically", // description: "Custom headers for the message. If reference message is set then In-Reply-To and References headers are set automatically",
type: [EmailHeaderDto], // type: [EmailHeaderDto],
}) // })
headers?: EmailHeaderDto[]; headers?: EmailHeaderDto[];
@IsOptional() @IsOptional()
+8 -2
View File
@@ -8,6 +8,7 @@ import { SearchMessagesQueryDto } from "./DTO/email-query.dto";
import { SendEmailDto, UpdateDraftDto } from "./DTO/send-email.dto"; import { SendEmailDto, UpdateDraftDto } from "./DTO/send-email.dto";
import { EmailService } from "./services/email.service"; import { EmailService } from "./services/email.service";
import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
import { BusinessDec } from "../../common/decorators/business.decorator";
import { UserDec } from "../../common/decorators/user.decorator"; import { UserDec } from "../../common/decorators/user.decorator";
@AuthGuards() @AuthGuards()
@@ -24,8 +25,13 @@ export class EmailController {
@ApiResponse({ status: 201, description: "Email sent successfully" }) @ApiResponse({ status: 201, description: "Email sent successfully" })
@ApiResponse({ status: 400, description: "Invalid email data or missing required fields for email type" }) @ApiResponse({ status: 400, description: "Invalid email data or missing required fields for email type" })
@ApiResponse({ status: 403, description: "Not authorized to send email" }) @ApiResponse({ status: 403, description: "Not authorized to send email" })
sendEmail(@UserDec("wildduckUserId") userEmailId: string, @Body() sendEmailDto: SendEmailDto) { sendEmail(
return this.emailService.sendEmail(userEmailId, sendEmailDto); @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") @Get("status/:queueId")
@@ -2,7 +2,7 @@ import { Injectable } from "@nestjs/common";
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
import { EmailHeaderDto } from "../DTO/send-email.dto"; import { EmailHeaderDto } from "../DTO/send-email.dto";
import { EmailHeaderKey } from "../enums/email-header.enum"; import { EmailHeaderKey, Priority } from "../enums/email-header.enum";
import { import {
IGenerateAntiThreadingHeadersOptions, IGenerateAntiThreadingHeadersOptions,
IGenerateGmailAntiThreadingHeadersOptions, IGenerateGmailAntiThreadingHeadersOptions,
@@ -16,17 +16,10 @@ export class EmailHeadersService {
generateAntiThreadingHeaders(options: IGenerateAntiThreadingHeadersOptions): EmailHeaderDto[] { generateAntiThreadingHeaders(options: IGenerateAntiThreadingHeadersOptions): EmailHeaderDto[] {
const headers: 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 // Add References header with unique value to break threading
headers.push({ headers.push({
key: EmailHeaderKey.References, key: EmailHeaderKey.References,
value: `<${uuidv4()}@${options.businessDomain || "localhost"}>`, value: `<${uuidv4()}@${options.businessDomain}>`,
}); });
// Add unique Thread-Index to prevent Outlook threading // Add unique Thread-Index to prevent Outlook threading
@@ -38,17 +31,17 @@ export class EmailHeadersService {
// Anti-spam headers // Anti-spam headers
headers.push({ headers.push({
key: EmailHeaderKey.XMailer, key: EmailHeaderKey.XMailer,
value: `${options.businessName || "Email Service"} v1.0`, value: `Danak Mail Service v1.0`,
}); });
headers.push({ headers.push({
key: EmailHeaderKey.XPriority, key: EmailHeaderKey.XPriority,
value: this.getPriorityValue(options.priority || "normal"), value: this.getPriorityValue(options.priority || Priority.Normal),
}); });
headers.push({ headers.push({
key: EmailHeaderKey.XMSMailPriority, key: EmailHeaderKey.XMSMailPriority,
value: options.priority || "Normal", value: options.priority || Priority.Normal,
}); });
// Add MIME version // Add MIME version
@@ -57,13 +50,27 @@ export class EmailHeadersService {
value: "1.0", value: "1.0",
}); });
// Add authentication headers
if (options.businessDomain) {
headers.push({ headers.push({
key: EmailHeaderKey.AuthenticationResults, key: EmailHeaderKey.XSpamStatus,
value: `${options.businessDomain}; dkim=pass; spf=pass; dmarc=pass`, 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 // Content classification headers
if (options.isTransactional) { 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 // Add custom headers to help with deliverability
// headers.push({ // headers.push({
// key: "X-Originating-IP", // key: "X-Originating-IP",
// value: "[" + this.getServerIP() + "]", // 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; return headers;
} }
+8 -11
View File
@@ -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(", ")}`); this.logger.log(`Sending ${EmailType.GENERAL} email from user ${userEmailId} to ${sendEmailDto.to.map((t) => t.address).join(", ")}`);
try { try {
const user = await this.userRepository.findOne({ wildduckUserId: userEmailId, deletedAt: null }, { populate: ["business"] }); this.logger.log(`Processing email through business template for business: ${businessId}`);
if (!user) throw new BadRequestException(EmailMessage.USER_NOT_FOUND);
this.logger.log(`Processing email through business template for business: ${user.business.id}`);
// Process email content through business template // 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, text: sendEmailDto.text,
html: sendEmailDto.html, html: sendEmailDto.html,
subject: sendEmailDto.subject, subject: sendEmailDto.subject,
@@ -57,19 +53,20 @@ export class EmailService {
} }
if (processedTemplate.hasTemplate) { 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 = { sendEmailDto.from = {
address: user.emailAddress, address: user.emailAddress,
name: user.displayName, name: user.displayName,
}; };
// Generate headers based on email type
const headers = this.generateHeadersForEmailType(user); const headers = this.generateHeadersForEmailType(user);
// Merge with existing headers if any sendEmailDto.headers = [...headers];
sendEmailDto.headers = [...(sendEmailDto.headers || []), ...headers];
const response = await firstValueFrom(this.mailServerService.submission.submitMessage(userEmailId, sendEmailDto)); const response = await firstValueFrom(this.mailServerService.submission.submitMessage(userEmailId, sendEmailDto));