diff --git a/package.json b/package.json index 7dce23a..5bf82df 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@xtr-dev/payload-mailing", - "version": "0.1.11", + "version": "0.1.12", "description": "Template-based email system with scheduling and job processing for PayloadCMS", "type": "module", "main": "dist/index.js", diff --git a/src/jobs/sendEmailTask.ts b/src/jobs/sendEmailTask.ts index 0c808af..ac8e067 100644 --- a/src/jobs/sendEmailTask.ts +++ b/src/jobs/sendEmailTask.ts @@ -1,6 +1,5 @@ import { sendEmail } from '../sendEmail.js' -import { Email } from '../payload-types.js' -import {BaseEmail} from "../types/index.js" +import {Email, EmailTemplate} from '../payload-types.js' export interface SendEmailTaskInput { // Template mode fields @@ -23,6 +22,45 @@ export interface SendEmailTaskInput { [key: string]: any } +/** + * Transforms task input into sendEmail options by separating template and data fields + */ +function transformTaskInputToSendEmailOptions(taskInput: SendEmailTaskInput) { + const sendEmailOptions: any = { + data: {} + } + + // If using template mode, set template options + if (taskInput.templateSlug) { + sendEmailOptions.template = { + slug: taskInput.templateSlug, + variables: taskInput.variables || {} + } + } + + // Standard email fields that should be copied to data + const standardFields = ['to', 'cc', 'bcc', 'subject', 'html', 'text', 'scheduledAt', 'priority'] + + // Template-specific fields that should not be copied to data + const templateFields = ['templateSlug', 'variables'] + + // Copy standard fields to data + standardFields.forEach(field => { + if (taskInput[field] !== undefined) { + sendEmailOptions.data[field] = taskInput[field] + } + }) + + // Copy any additional custom fields that aren't template or standard fields + Object.keys(taskInput).forEach(key => { + if (!templateFields.includes(key) && !standardFields.includes(key)) { + sendEmailOptions.data[key] = taskInput[key] + } + }) + + return sendEmailOptions +} + export const sendEmailJob = { slug: 'send-email', label: 'Send Email', @@ -128,40 +166,11 @@ export const sendEmailJob = { const taskInput = input as SendEmailTaskInput try { - // Prepare options for sendEmail based on task input - const sendEmailOptions: any = { - data: {} - } - - // If using template mode - if (taskInput.templateSlug) { - sendEmailOptions.template = { - slug: taskInput.templateSlug, - variables: taskInput.variables || {} - } - } - - // Build data object from task input - const dataFields = ['to', 'cc', 'bcc', 'subject', 'html', 'text', 'scheduledAt', 'priority'] - const additionalFields: string[] = [] - - // Copy standard fields - dataFields.forEach(field => { - if (taskInput[field] !== undefined) { - sendEmailOptions.data[field] = taskInput[field] - } - }) - - // Copy any additional custom fields - Object.keys(taskInput).forEach(key => { - if (!['templateSlug', 'variables', ...dataFields].includes(key)) { - sendEmailOptions.data[key] = taskInput[key] - additionalFields.push(key) - } - }) + // Transform task input into sendEmail options using helper function + const sendEmailOptions = transformTaskInputToSendEmailOptions(taskInput) // Use the sendEmail helper to create the email - const email = await sendEmail(payload, sendEmailOptions) + const email = await sendEmail(payload, sendEmailOptions) return { output: { @@ -171,8 +180,15 @@ export const sendEmailJob = { } } catch (error) { - const errorMessage = error instanceof Error ? error.message : 'Unknown error' - throw new Error(`Failed to queue email: ${errorMessage}`) + if (error instanceof Error) { + // Preserve original error and stack trace + const wrappedError = new Error(`Failed to queue email: ${error.message}`) + wrappedError.stack = error.stack + wrappedError.cause = error + throw wrappedError + } else { + throw new Error(`Failed to queue email: ${String(error)}`) + } } } } diff --git a/src/sendEmail.ts b/src/sendEmail.ts index e83052f..25f4616 100644 --- a/src/sendEmail.ts +++ b/src/sendEmail.ts @@ -1,10 +1,9 @@ import { Payload } from 'payload' import { getMailing, renderTemplate, parseAndValidateEmails } from './utils/helpers.js' -import {Email} from "./payload-types.js" -import {BaseEmail} from "./types/index.js" +import {Email, EmailTemplate} from "./payload-types.js" // Options for sending emails -export interface SendEmailOptions { +export interface SendEmailOptions { // Template-based email template?: { slug: string @@ -36,14 +35,14 @@ export interface SendEmailOptions { * }) * ``` */ -export const sendEmail = async ( +export const sendEmail = async ( payload: Payload, - options: SendEmailOptions -): Promise => { + options: SendEmailOptions +): Promise => { const mailing = getMailing(payload) const collectionSlug = options.collectionSlug || mailing.collections.emails || 'emails' - let emailData: Partial = { ...options.data } as Partial + let emailData: Partial = { ...options.data } as Partial // If using a template, render it first if (options.template) { @@ -59,7 +58,7 @@ export const sendEmail = async + } as Partial } // Validate required fields @@ -97,7 +96,12 @@ export const sendEmail = async { + private async getTemplateBySlug(templateSlug: string): Promise { try { const { docs } = await this.payload.find({ collection: this.templatesCollection as any, @@ -314,7 +312,7 @@ export class MailingService implements IMailingService { limit: 1, }) - return docs.length > 0 ? docs[0] as EmailTemplate : null + return docs.length > 0 ? docs[0] as BaseEmailTemplate : null } catch (error) { console.error(`Template with slug '${templateSlug}' not found:`, error) return null @@ -379,7 +377,7 @@ export class MailingService implements IMailingService { }) } - private async renderEmailTemplate(template: EmailTemplate, variables: Record = {}): Promise<{ html: string; text: string }> { + private async renderEmailTemplate(template: BaseEmailTemplate, variables: Record = {}): Promise<{ html: string; text: string }> { if (!template.content) { return { html: '', text: '' } } diff --git a/src/types/index.ts b/src/types/index.ts index c8a997c..d14d8d7 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,9 +1,11 @@ import { Payload } from 'payload' import type { CollectionConfig, RichTextField } from 'payload' import { Transporter } from 'nodemailer' -import {Email} from "../payload-types.js" +import {Email, EmailTemplate} from "../payload-types.js" -export type BaseEmail = Omit & {template: Omit} +export type BaseEmail = Omit & {template: Omit | TEmailTemplate['id'] | undefined | null} + +export type BaseEmailTemplate = Omit export type TemplateRendererHook = (template: string, variables: Record) => string | Promise @@ -37,16 +39,6 @@ export interface MailingTransportConfig { } } -export interface EmailTemplate { - id: string - name: string - slug: string - subject: string - content: any // Lexical editor state - createdAt: string - updatedAt: string -} - export interface QueuedEmail { id: string