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] })
|
@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,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user