chore: change email header
This commit is contained in:
@@ -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,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user