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] })
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()
+8 -2
View File
@@ -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")
@@ -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;
}
+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(", ")}`);
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));