From 2deefc8eaa3a09d04eebabad8092e5e11f58c0a4 Mon Sep 17 00:00:00 2001 From: Bas van den Aakster Date: Sat, 13 Sep 2025 18:51:46 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20FEATURE:=20Add=20PayloadCMS=20task?= =?UTF-8?q?=20for=20queuing=20template=20emails?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add sendTemplateEmailTask with comprehensive input schema - Support template rendering, email parsing, and scheduling - Include TypeScript interface SendTemplateEmailInput for type safety - Add task to exports for easy import and usage - Support custom email collection fields via extensible input - Add comprehensive documentation with usage examples Users can now: ✅ Import and add task to their Payload jobs configuration ✅ Queue emails programmatically via payload.jobs.queue() ✅ Use admin panel form interface for manual email queuing ✅ Get full TypeScript support with proper input types ✅ Extend with custom fields from their email collection Example usage: ```typescript import { sendTemplateEmailTask } from '@xtr-dev/payload-mailing' // Add to Payload config export default buildConfig({ jobs: { tasks: [sendTemplateEmailTask] } }) // Queue from code await payload.jobs.queue({ task: 'send-template-email', input: { templateSlug: 'welcome', to: ['user@example.com'], variables: { name: 'John' } } }) ``` 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- README.md | 75 ++++++++++++++ package.json | 2 +- src/index.ts | 4 + src/tasks/sendTemplateEmailTask.ts | 151 +++++++++++++++++++++++++++++ 4 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 src/tasks/sendTemplateEmailTask.ts diff --git a/README.md b/README.md index 3976a96..181a886 100644 --- a/README.md +++ b/README.md @@ -598,6 +598,81 @@ await processEmails(payload) await retryFailedEmails(payload) ``` +## PayloadCMS Task Integration + +The plugin provides a ready-to-use PayloadCMS task for queuing template emails: + +### 1. Add the task to your Payload config + +```typescript +import { buildConfig } from 'payload/config' +import { sendTemplateEmailTask } from '@xtr-dev/payload-mailing' + +export default buildConfig({ + // ... your config + jobs: { + tasks: [ + sendTemplateEmailTask, + // ... your other tasks + ] + } +}) +``` + +### 2. Queue emails from your code + +```typescript +import type { SendTemplateEmailInput } from '@xtr-dev/payload-mailing' + +// Queue a template email +const result = await payload.jobs.queue({ + task: 'send-template-email', + input: { + templateSlug: 'welcome-email', + to: ['user@example.com'], + cc: ['manager@example.com'], + variables: { + firstName: 'John', + activationUrl: 'https://example.com/activate/123' + }, + priority: 1, + // Add any custom fields from your email collection + customField: 'value' + } as SendTemplateEmailInput +}) + +// Queue a scheduled email +await payload.jobs.queue({ + task: 'send-template-email', + input: { + templateSlug: 'reminder-email', + to: ['user@example.com'], + variables: { eventName: 'Product Launch' }, + scheduledAt: new Date('2024-01-15T10:00:00Z').toISOString(), + priority: 3 + } +}) +``` + +### 3. Use in admin panel workflows + +The task can also be triggered from the Payload admin panel with a user-friendly form interface that includes: +- Template slug selection +- Email recipients (to, cc, bcc) +- Template variables as JSON +- Optional scheduling +- Priority setting +- Any custom fields you've added to your email collection + +### Task Benefits + +- ✅ **Easy Integration**: Just add to your tasks array +- ✅ **Type Safety**: Full TypeScript support with `SendTemplateEmailInput` +- ✅ **Admin UI**: Ready-to-use form interface +- ✅ **Flexible**: Supports all your custom email collection fields +- ✅ **Error Handling**: Comprehensive error reporting +- ✅ **Queue Management**: Leverage Payload's job queue system + ## Job Processing The plugin automatically adds a unified email processing job to PayloadCMS: diff --git a/package.json b/package.json index e19c11b..e5426a8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@xtr-dev/payload-mailing", - "version": "0.1.2", + "version": "0.1.3", "description": "Template-based email system with scheduling and job processing for PayloadCMS", "type": "module", "main": "dist/index.js", diff --git a/src/index.ts b/src/index.ts index 74cc6bb..f642991 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,6 +11,10 @@ export { MailingService } from './services/MailingService.js' export { default as EmailTemplates, createEmailTemplatesCollection } from './collections/EmailTemplates.js' export { default as Emails } from './collections/Emails.js' +// Tasks +export { sendTemplateEmailTask, default as sendTemplateEmailTaskDefault } from './tasks/sendTemplateEmailTask.js' +export type { SendTemplateEmailInput } from './tasks/sendTemplateEmailTask.js' + // Jobs are integrated into the plugin configuration // Utility functions for developers diff --git a/src/tasks/sendTemplateEmailTask.ts b/src/tasks/sendTemplateEmailTask.ts new file mode 100644 index 0000000..b192c6f --- /dev/null +++ b/src/tasks/sendTemplateEmailTask.ts @@ -0,0 +1,151 @@ +import { renderTemplate } from '../utils/helpers.js' + +export interface SendTemplateEmailInput { + templateSlug: string + to: string | string[] + cc?: string | string[] + bcc?: string | string[] + variables?: Record + scheduledAt?: string // ISO date string + priority?: number + // Allow any additional fields that users might have in their email collection + [key: string]: any +} + +export const sendTemplateEmailTask = { + slug: 'send-template-email', + label: 'Send Template Email', + inputSchema: [ + { + name: 'templateSlug', + type: 'text' as const, + required: true, + label: 'Template Slug', + admin: { + description: 'The slug of the email template to render' + } + }, + { + name: 'to', + type: 'text' as const, + required: true, + label: 'To (Email Recipients)', + admin: { + description: 'Comma-separated list of email addresses' + } + }, + { + name: 'cc', + type: 'text' as const, + label: 'CC (Carbon Copy)', + admin: { + description: 'Optional comma-separated list of CC email addresses' + } + }, + { + name: 'bcc', + type: 'text' as const, + label: 'BCC (Blind Carbon Copy)', + admin: { + description: 'Optional comma-separated list of BCC email addresses' + } + }, + { + name: 'variables', + type: 'json' as const, + label: 'Template Variables', + admin: { + description: 'JSON object with variables for template rendering' + } + }, + { + name: 'scheduledAt', + type: 'date' as const, + label: 'Schedule For', + admin: { + description: 'Optional date/time to schedule email for future delivery' + } + }, + { + name: 'priority', + type: 'number' as const, + label: 'Priority', + min: 1, + max: 10, + admin: { + description: 'Email priority (1 = highest, 10 = lowest)' + } + } + ], + handler: async ({ input, payload }: any) => { + // Cast input to our expected type + const taskInput = input as SendTemplateEmailInput + + try { + // Render the template + const { html, text, subject } = await renderTemplate( + payload, + taskInput.templateSlug, + taskInput.variables || {} + ) + + // Parse email addresses + const parseEmails = (emails: string | string[] | undefined): string[] | undefined => { + if (!emails) return undefined + if (Array.isArray(emails)) return emails + return emails.split(',').map(email => email.trim()).filter(Boolean) + } + + // Prepare email data + const emailData: any = { + to: parseEmails(taskInput.to), + cc: parseEmails(taskInput.cc), + bcc: parseEmails(taskInput.bcc), + subject, + html, + text, + priority: taskInput.priority || 5, + } + + // Add scheduled date if provided + if (taskInput.scheduledAt) { + emailData.scheduledAt = new Date(taskInput.scheduledAt).toISOString() + } + + // Add any additional fields from input (excluding the ones we've already handled) + const handledFields = ['templateSlug', 'to', 'cc', 'bcc', 'variables', 'scheduledAt', 'priority'] + Object.keys(taskInput).forEach(key => { + if (!handledFields.includes(key)) { + emailData[key] = taskInput[key] + } + }) + + // Create the email in the collection + const email = await payload.create({ + collection: 'emails', // Default collection name + data: emailData + }) + + return { + success: true, + emailId: email.id, + message: `Email queued successfully with ID: ${email.id}`, + templateSlug: taskInput.templateSlug, + recipients: emailData.to?.length || 0, + scheduledAt: emailData.scheduledAt || null + } + + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error' + + return { + success: false, + error: errorMessage, + templateSlug: taskInput.templateSlug, + message: `Failed to queue email: ${errorMessage}` + } + } + } +} + +export default sendTemplateEmailTask \ No newline at end of file