From 7e65f049fc3ca091960b446aa1daad0d5a9e2fb1 Mon Sep 17 00:00:00 2001 From: mahyargdz Date: Mon, 4 Aug 2025 16:40:49 +0330 Subject: [PATCH] fix: bug in the template --- src/modules/email/DTO/send-email.dto.ts | 91 +------------------- src/modules/email/services/email.service.ts | 93 +++++++++------------ 2 files changed, 43 insertions(+), 141 deletions(-) diff --git a/src/modules/email/DTO/send-email.dto.ts b/src/modules/email/DTO/send-email.dto.ts index 7cc97bc..a651d74 100644 --- a/src/modules/email/DTO/send-email.dto.ts +++ b/src/modules/email/DTO/send-email.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional, PartialType } from "@nestjs/swagger"; -import { Type } from "class-transformer"; +import { Exclude, Type } from "class-transformer"; import { IsArray, IsBoolean, @@ -120,10 +120,7 @@ export class EmailEnvelopeDto { //######################################################## export class SendEmailDto { - // @IsNotEmpty({ message: EmailMessage.FROM_ADDRESS_REQUIRED }) - // @ValidateNested() - // @Type(() => EmailRecipientDto) - // @ApiProperty({ description: "Address for the From: header" }) + @Exclude() from: EmailRecipientDto; @IsOptional() @@ -153,17 +150,9 @@ 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], - // }) + @Exclude() headers?: EmailHeaderDto[]; - // @IsOptional() @IsNotEmpty({ message: EmailMessage.SUBJECT_REQUIRED }) @IsString({ message: EmailMessage.SUBJECT_MUST_BE_STRING }) @ApiPropertyOptional({ description: "Message subject. If not then resolved from Reference message (optional)", example: "Test email" }) @@ -188,24 +177,6 @@ export class SendEmailDto { @ApiPropertyOptional({ description: "Attachments for the message (optional)", type: [EmailAttachmentDto] }) attachments?: EmailAttachmentDto[]; - @IsOptional() - @ApiPropertyOptional({ description: "Optional metadata, must be an object or JSON formatted string (optional)" }) - meta?: Record; - - @IsOptional() - @IsString({ message: EmailMessage.SESSION_ID_MUST_BE_STRING }) - @ApiPropertyOptional({ description: "Session identifier for the logs (optional)" }) - sess?: string; - - @IsOptional() - @IsString({ message: EmailMessage.IP_ADDRESS_MUST_BE_STRING }) - @ApiPropertyOptional({ description: "IP address for the logs (optional)" }) - ip?: string; - - // @IsOptional() - // @ApiPropertyOptional({ description: "Reference message data (optional)" }) - // reference?: any; - @IsOptional() @IsBoolean({ message: EmailMessage.IS_DRAFT_MUST_BE_BOOLEAN }) @ApiPropertyOptional({ description: "Is the message a draft or not (optional)", default: false }) @@ -237,62 +208,6 @@ export class SendEmailDto { @Type(() => EmailEnvelopeDto) @ApiPropertyOptional({ description: "Optional envelope (optional)" }) envelope?: EmailEnvelopeDto; - - // @IsOptional() - // @IsEnum(EmailType, { message: "Email type must be one of: general, transactional, marketing, anti-threading" }) - // @ApiPropertyOptional({ - // description: "Email type for appropriate header configuration", - // enum: EmailType, - // default: EmailType.GENERAL, - // example: EmailType.TRANSACTIONAL, - // }) - // emailType?: EmailType; - - // @IsOptional() - // @IsString({ message: EmailMessage.HEADER_VALUE_MUST_BE_STRING }) - // @ApiPropertyOptional({ description: "Transaction ID for transactional emails (optional)", example: "ORDER-123456" }) - // transactionId?: string; - - // @IsOptional() - // @IsString({ message: EmailMessage.HEADER_VALUE_MUST_BE_STRING }) - // @ApiPropertyOptional({ description: "Category for transactional emails (optional)", example: "order-confirmation" }) - // category?: string; - - // @IsOptional() - // @IsString({ message: EmailMessage.HEADER_VALUE_MUST_BE_STRING }) - // @ApiPropertyOptional({ description: "List ID for marketing emails (optional)", example: "newsletter-subscribers" }) - // listId?: string; - - // @IsOptional() - // @IsString({ message: EmailMessage.HEADER_VALUE_MUST_BE_STRING }) - // @ApiPropertyOptional({ description: "Unsubscribe URL for marketing emails (optional)", example: "https://yourdomain.com/unsubscribe?token=xyz" }) - // unsubscribeUrl?: string; - - // @IsOptional() - // @IsString({ message: EmailMessage.HEADER_VALUE_MUST_BE_STRING }) - // @ApiPropertyOptional({ description: "Campaign ID for marketing emails (optional)", example: "CAMPAIGN-2024-001" }) - // campaignId?: string; - - // @IsOptional() - // @IsEnum(Priority, { message: EmailMessage.PRIORITY_MUST_BE_ONE_OF_HIGH_NORMAL_LOW }) - // @ApiPropertyOptional({ description: "Email priority (optional)", enum: Priority, default: Priority.Normal }) - // priority?: Priority; - - // @IsOptional() - // @IsBoolean({ message: EmailMessage.IS_DRAFT_MUST_BE_BOOLEAN }) - // @ApiPropertyOptional({ description: "Force Gmail thread break for anti-threading emails (optional)", default: false }) - // forceGmailBreak?: boolean; - - // @IsOptional() - // @IsString({ message: EmailMessage.HEADER_VALUE_MUST_BE_STRING }) - // @ApiPropertyOptional({ description: "Conversation breaker for anti-threading emails (optional)" }) - // conversationBreaker?: string; - - // @IsOptional() - // @IsNotEmpty() - // @IsString({ message: EmailMessage.MAILBOX_MUST_BE_STRING }) - // @ApiPropertyOptional({ description: "ID of the Mailbox (optional)", example: "507f1f77bcf86cd799439012" }) - // mailbox?: string; } //######################################################## diff --git a/src/modules/email/services/email.service.ts b/src/modules/email/services/email.service.ts index 53b5211..3973348 100644 --- a/src/modules/email/services/email.service.ts +++ b/src/modules/email/services/email.service.ts @@ -10,7 +10,6 @@ import { EmailMessage } from "../../../common/enums/message.enum"; import { EmailNotificationService } from "../../email-utils/services/email-notification.service"; import { MailboxResolverService } from "../../email-utils/services/mailbox-resolver.service"; import { SearchMessagesMailServerQueryDto } from "../../mail-server/DTO/message.dto"; -import { MessageHeaders } from "../../mail-server/interfaces/messages-response.interface"; import { MailServerService } from "../../mail-server/services/mail-server.service"; import { TemplateProcessorService } from "../../templates/services/template-processor.service"; import { User } from "../../users/entities/user.entity"; @@ -18,7 +17,7 @@ import { UserRepository } from "../../users/repositories/user.repository"; import { EMAIL_QUEUE_CONSTANTS } from "../constants/email-events.constant"; import { BulkActionDto, BulkActionType } from "../DTO/bulk-actions.dto"; import { MessageListQueryDto, SearchMessagesQueryDto } from "../DTO/email-query.dto"; -import { EmailRecipientDto, SendEmailDto, UpdateDraftDto } from "../DTO/send-email.dto"; +import { EmailAttachmentDto, EmailRecipientDto, SendEmailDto, UpdateDraftDto } from "../DTO/send-email.dto"; import { PriorityEnum } from "../enums/email-header.enum"; import { IEmailMap } from "../interfaces/email-map.interface"; @@ -477,7 +476,7 @@ export class EmailService { } //============================================== - async updateDraft(wildduckUserId: string, messageId: number, updateDraftDto: UpdateDraftDto) { + async updateDraft(wildduckUserId: string, messageId: number, updateDraftDto: UpdateDraftDto, generateTemplate: boolean = false) { const draftsMailboxId = await this.mailboxResolverService.getDraftsMailboxId(wildduckUserId); const existingDraft = await firstValueFrom(this.mailServerService.messages.getMessage(wildduckUserId, draftsMailboxId, messageId)); @@ -490,41 +489,41 @@ export class EmailService { cc: updateDraftDto.cc, bcc: updateDraftDto.bcc, replyTo: updateDraftDto.replyTo, - headers: updateDraftDto.headers, subject: updateDraftDto.subject, text: updateDraftDto.text, html: updateDraftDto.html, attachments: updateDraftDto.attachments, - meta: updateDraftDto.meta, - sess: updateDraftDto.sess, - ip: updateDraftDto.ip, isDraft: true, uploadOnly: true, }; - // const user = await this.userRepository.findOne({ wildduckUserId, deletedAt: null }, { populate: ["business"] }); + if (generateTemplate) { + const user = await this.userRepository.findOne({ wildduckUserId, deletedAt: null }, { populate: ["business"] }); - // if (user && user.business && (updateDraftDto.text || updateDraftDto.html)) { - // const processedTemplate = await this.templateProcessorService.processEmailContent(user.business.id, user.id, { - // text: updateDraftDto.text!, - // html: updateDraftDto.html!, - // subject: updateDraftDto.subject, - // }); + if (!user || !user.business) throw new BadRequestException(EmailMessage.USER_NOT_FOUND); - // if (processedTemplate.isHtml) { - // draftData.html = processedTemplate.content; - // draftData.text = ""; - // } else { - // draftData.text = processedTemplate.content; - // draftData.html = ""; - // } + if (updateDraftDto.text || updateDraftDto.html) { + const processedTemplate = await this.templateProcessorService.processEmailContent(user.business.id, user.id, { + text: updateDraftDto.text!, + html: updateDraftDto.html!, + subject: updateDraftDto.subject, + }); - // const headers = this.generateHeadersForEmailType(user, { - // html: draftData.html, - // text: draftData.text, - // }); - // draftData.headers = [...(draftData.headers || []), ...headers]; - // } + if (processedTemplate.isHtml) { + draftData.html = processedTemplate.content; + draftData.text = ""; + } else { + draftData.text = processedTemplate.content; + draftData.html = ""; + } + + const headers = this.generateHeadersForEmailType(user, { + html: draftData.html, + text: draftData.text, + }); + draftData.headers = [...(draftData.headers || []), ...headers]; + } + } const response = await firstValueFrom(this.mailServerService.submission.submitMessage(wildduckUserId, draftData as SendEmailDto)); @@ -540,38 +539,26 @@ export class EmailService { const draftsMailboxId = await this.mailboxResolverService.getDraftsMailboxId(wildduckUserId); const message = await firstValueFrom(this.mailServerService.messages.getMessage(wildduckUserId, draftsMailboxId, messageId)); - - const user = await this.userRepository.findOne({ wildduckUserId, deletedAt: null }, { populate: ["business"] }); - - if (!user) throw new BadRequestException(EmailMessage.USER_NOT_FOUND); - - const processedTemplate = await this.templateProcessorService.processEmailContent(user.business.id, user.id, { - text: message.text || "", - html: message.html?.[0] || "", + const updateMessage: UpdateDraftDto = { + from: message.from, + to: message.to, + cc: message.cc, + bcc: message.bcc, + replyTo: message.inReplyTo ? { address: message.inReplyTo } : undefined, subject: message.subject, - }); - - if (processedTemplate.isHtml) { - message.html = [processedTemplate.content]; - message.text = ""; - } else { - message.text = processedTemplate.content; - message.html = []; - } - - const headers = this.generateHeadersForEmailType(user, { + text: message.text, html: message.html?.[0] || "", - text: message.text || "", - }); - message.headers = { ...message.headers, ...headers } as unknown as MessageHeaders; + attachments: message.attachments as unknown as EmailAttachmentDto[], + isDraft: true, + uploadOnly: true, + }; + + const { messageId: updatedMessageId } = await this.updateDraft(wildduckUserId, messageId, updateMessage, true); const result = await firstValueFrom( - this.mailServerService.messages.submitDraft(wildduckUserId, draftsMailboxId, messageId, { deleteFiles: false }), + this.mailServerService.messages.submitDraft(wildduckUserId, draftsMailboxId, Number(updatedMessageId), { deleteFiles: false }), ); - await firstValueFrom(this.mailServerService.messages.deleteMessage(wildduckUserId, draftsMailboxId, messageId)); - this.logger.log(`Draft message ${messageId} deleted from drafts folder after sending`); - await this.emailNotificationService.notifyEmailSentFromDraft({ wildduckUserId, userId,