diff --git a/package.json b/package.json index 59d26cc..7a6cd1d 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,6 @@ "@types/nodemailer": "^6.4.17", "@types/passport-jwt": "^4.0.1", "@types/supertest": "^6.0.3", - "@types/web-push": "^3.6.4", "@typescript-eslint/eslint-plugin": "^8.35.1", "@typescript-eslint/parser": "^8.35.1", "eslint": "^9.30.1", diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index 441f908..3e66ae0 100755 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -281,6 +281,8 @@ export const enum EmailMessage { TRASH_EMPTYED_SUCCESSFULLY = "سطل زباله با موفقیت خالی شد", SPAM_EMPTYED_SUCCESSFULLY = "همه پیام های اسپم با موفقیت حذف شدند", SAVE_DRAFT_SUCCESS = "پیش نویس با موفقیت ذخیره شد", + HTML_CONTENT_REQUIRED = "محتوای HTML مورد نیاز است", + TEXT_CONTENT_REQUIRED = "TEXT_CONTENT_REQUIRED", } export const enum WebSocketMessage { diff --git a/src/modules/email-utils/queue/email-polling.processor.ts b/src/modules/email-utils/queue/email-polling.processor.ts index ed67f59..8654f65 100644 --- a/src/modules/email-utils/queue/email-polling.processor.ts +++ b/src/modules/email-utils/queue/email-polling.processor.ts @@ -76,7 +76,6 @@ export class EmailPollingProcessor extends WorkerProcessor { timestamp: email.date || new Date().toISOString(), mailboxId: email.mailbox, mailboxName: "Inbox", - isRead: !!email.seen, hasAttachments: !!(email.attachments && Array.isArray(email.attachments) && email.attachments.length > 0), }); diff --git a/src/modules/email-utils/services/email-notification.service.ts b/src/modules/email-utils/services/email-notification.service.ts index d41de90..3c74b3e 100644 --- a/src/modules/email-utils/services/email-notification.service.ts +++ b/src/modules/email-utils/services/email-notification.service.ts @@ -80,6 +80,16 @@ export class EmailNotificationService { } } + //************************************************************************ */ + async notifyEmailSentFromDraft(emailSentFromDraftData: IEmailSentPayload) { + try { + await this.emailGateway.notifyEmailSent(emailSentFromDraftData.userId, emailSentFromDraftData); + await this.updateUnreadCount(emailSentFromDraftData.wildduckUserId, emailSentFromDraftData.userId); + } catch (error) { + this.logger.error(`Failed to notify email sent from draft for user ${emailSentFromDraftData.userId}:`, error); + } + } + //************************************************************************ */ async notifyEmailDeleted(emailDeletedData: IEmailDeletePayload) { try { diff --git a/src/modules/email/DTO/send-email.dto.ts b/src/modules/email/DTO/send-email.dto.ts index 375a2d2..f8ac7cc 100644 --- a/src/modules/email/DTO/send-email.dto.ts +++ b/src/modules/email/DTO/send-email.dto.ts @@ -157,15 +157,15 @@ export class SendEmailDto { @ApiPropertyOptional({ description: "Message subject. If not then resolved from Reference message (optional)", example: "Test email" }) subject: string; - @IsOptional() + @IsNotEmpty({ message: EmailMessage.TEXT_CONTENT_REQUIRED }) @IsString({ message: EmailMessage.TEXT_CONTENT_MUST_BE_STRING }) @ApiPropertyOptional({ description: "Plaintext message (optional)" }) - text?: string; + text: string; - @IsOptional() + @IsNotEmpty({ message: EmailMessage.HTML_CONTENT_REQUIRED }) @IsString({ message: EmailMessage.HTML_CONTENT_MUST_BE_STRING }) @ApiPropertyOptional({ description: "HTML formatted message (optional)" }) - html?: string; + html: string; @IsOptional() @IsArray({ message: EmailMessage.ATTACHMENTS_MUST_BE_ARRAY }) diff --git a/src/modules/email/email.controller.ts b/src/modules/email/email.controller.ts index f2f761e..a257cd8 100644 --- a/src/modules/email/email.controller.ts +++ b/src/modules/email/email.controller.ts @@ -207,8 +207,8 @@ export class EmailController { @ApiResponse({ status: 200, description: "Draft sent successfully" }) @ApiResponse({ status: 404, description: "Draft not found" }) @ApiResponse({ status: 403, description: "Not authorized to send draft" }) - sendDraft(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto) { - return this.emailService.sendDraft(userId, Number(params.messageId)); + sendDraft(@UserDec() user: User, @Param() params: MessageParamDto) { + return this.emailService.sendDraft(user.wildduckUserId, user.id, Number(params.messageId)); } @Delete("messages/drafts/:messageId") diff --git a/src/modules/email/interfaces/email-events.interface.ts b/src/modules/email/interfaces/email-events.interface.ts index a8dc9d7..6479381 100644 --- a/src/modules/email/interfaces/email-events.interface.ts +++ b/src/modules/email/interfaces/email-events.interface.ts @@ -2,6 +2,8 @@ // BASE INTERFACES // ==================== +import { MessageAddress } from "../../mail-server/interfaces/messages-response.interface"; + export interface IBaseEventPayload { userId: string; timestamp: string; @@ -23,21 +25,15 @@ export interface IBaseMessageMailboxEventPayload extends IBaseMessageEventPayloa // COMMON EMAIL INTERFACES // ==================== -export interface IEmailContact { - name?: string; - address: string; -} - export interface IEmailMetadata { subject: string; hasAttachments: boolean; mailboxName: string; - isRead: boolean; } export interface IEmailContent extends IEmailMetadata { - from: IEmailContact; - to: IEmailContact[]; + from: MessageAddress; + to: MessageAddress[]; preview?: string; } diff --git a/src/modules/email/services/email.service.ts b/src/modules/email/services/email.service.ts index 4ca6fc1..b70f5ba 100644 --- a/src/modules/email/services/email.service.ts +++ b/src/modules/email/services/email.service.ts @@ -58,10 +58,10 @@ export class EmailService { if (processedTemplate.isHtml) { sendEmailDto.html = processedTemplate.content; - sendEmailDto.text = undefined; + sendEmailDto.text = ""; } else { sendEmailDto.text = processedTemplate.content; - sendEmailDto.html = undefined; + sendEmailDto.html = ""; } } @@ -86,7 +86,6 @@ export class EmailService { from: sendEmailDto.from, hasAttachments: false, mailboxName: "Sent", - isRead: true, timestamp: new Date().toISOString(), }); @@ -514,10 +513,10 @@ export class EmailService { if (processedTemplate.isHtml) { draftData.html = processedTemplate.content; - draftData.text = undefined; + draftData.text = ""; } else { draftData.text = processedTemplate.content; - draftData.html = undefined; + draftData.html = ""; } const headers = this.generateHeadersForEmailType(user, { @@ -538,12 +537,27 @@ export class EmailService { } //============================================== - async sendDraft(wildduckUserId: string, messageId: number) { + async sendDraft(wildduckUserId: string, userId: string, messageId: number) { const draftsMailboxId = await this.mailboxResolverService.getDraftsMailboxId(wildduckUserId); const result = await firstValueFrom( this.mailServerService.messages.submitDraft(wildduckUserId, draftsMailboxId, messageId, { deleteFiles: false }), ); + const message = await firstValueFrom(this.mailServerService.messages.getMessage(wildduckUserId, draftsMailboxId, messageId)); + + await this.emailNotificationService.notifyEmailSentFromDraft({ + wildduckUserId, + userId, + from: message.from, + to: message.to!, + subject: message.subject, + hasAttachments: false, + mailboxName: message.mailbox, + messageId: Number(message.id), + mailboxId: message.mailbox, + timestamp: new Date().toISOString(), + }); + return { message: EmailMessage.DRAFT_SENT_SUCCESSFULLY, messageId: result.message.id, diff --git a/src/modules/templates/interfaces/structure.interface.ts b/src/modules/templates/interfaces/structure.interface.ts index e71a253..bdd0305 100644 --- a/src/modules/templates/interfaces/structure.interface.ts +++ b/src/modules/templates/interfaces/structure.interface.ts @@ -180,7 +180,7 @@ export type PersonalityDataType = { }; export interface EmailContent { - text?: string; - html?: string; + text: string; + html: string; subject?: string; } diff --git a/src/modules/templates/services/template-processor.service.ts b/src/modules/templates/services/template-processor.service.ts index baf966e..f082101 100644 --- a/src/modules/templates/services/template-processor.service.ts +++ b/src/modules/templates/services/template-processor.service.ts @@ -18,7 +18,7 @@ export class TemplateProcessorService { if (!selectedTemplate) { return { - content: emailContent.text || emailContent.html || "", + content: emailContent.html || emailContent.text || "", isHtml: false, hasTemplate: false, }; @@ -45,23 +45,21 @@ export class TemplateProcessorService { //**************************************************** */ private async injectContentIntoTemplate(templateHtml: string | undefined, emailContent: EmailContent) { - if (!templateHtml) { - return emailContent.text || emailContent.html || ""; - } + const { html, text } = emailContent; + + if (!templateHtml) return html || text || ""; let processedHtml = templateHtml; - // Content to inject (prefer text, fallback to html) - const contentToInject = emailContent.text || this.stripHtmlTags(emailContent.html || ""); + // const contentToInject = this.stripHtmlTags(html || text); + const contentToInject = html || text; - // Replace content placeholders const contentPlaceholders = [/\{\{content\}\}/gi, /\{\{message\}\}/gi, /\{\{body\}\}/gi, /\{content\}/gi, /\{message\}/gi, /\{body\}/gi]; contentPlaceholders.forEach((placeholder) => { processedHtml = processedHtml.replace(placeholder, contentToInject); }); - // Replace subject placeholders if subject exists if (emailContent.subject) { const subjectPlaceholders = [/\{\{subject\}\}/gi, /\{\{title\}\}/gi, /\{subject\}/gi, /\{title\}/gi]; @@ -74,15 +72,15 @@ export class TemplateProcessorService { } //**************************************************** */ - private stripHtmlTags(html: string): string { - return html - .replace(/<[^>]*>/g, "") - .replace(/ /g, " ") - .replace(/&/g, "&") - .replace(/</g, "<") - .replace(/>/g, ">") - .replace(/"/g, '"') - .replace(/'/g, "'") - .trim(); - } + // private stripHtmlTags(html: string): string { + // return html + // .replace(/<[^>]*>/g, "") + // .replace(/ /g, " ") + // .replace(/&/g, "&") + // .replace(/</g, "<") + // .replace(/>/g, ">") + // .replace(/"/g, '"') + // .replace(/'/g, "'") + // .trim(); + // } }