BREAKING CHANGE: Remove sendEmailWorkflow, add immediate processing to sendEmailTask

- Remove entire workflows directory and sendEmailWorkflow
- Factor out email processing logic into reusable utilities (emailProcessor.ts)
- Add processImmediately option to sendEmailTask input schema
- Update sendEmailTask to process emails immediately when requested
- Update processEmailsTask to use shared processing utilities
- Remove workflow-related exports and plugin configuration
- Simplify documentation to focus on unified task approach
- Export new email processing utilities (processEmailById, processAllEmails)
- Bump version to 0.4.0 (breaking change - workflows removed)

Migration: Use sendEmailTask with processImmediately: true instead of sendEmailWorkflow
This commit is contained in:
2025-09-14 17:53:29 +02:00
parent 845b379da3
commit a12d4c1bee
9 changed files with 100 additions and 360 deletions

View File

@@ -1,5 +1,5 @@
import type { PayloadRequest, Payload } from 'payload'
import type { MailingService } from '../services/MailingService.js'
import { processAllEmails } from '../utils/emailProcessor.js'
/**
* Data passed to the process emails task
@@ -14,18 +14,16 @@ export interface ProcessEmailsTaskData {
*/
export const processEmailsTaskHandler = async (
job: { data: ProcessEmailsTaskData },
context: { req: PayloadRequest; mailingService: MailingService }
context: { req: PayloadRequest }
) => {
const { mailingService } = context
const { req } = context
const payload = (req as any).payload
try {
console.log('🔄 Processing email queue (pending + failed emails)...')
// Process pending emails first
await mailingService.processEmails()
// Then retry failed emails
await mailingService.retryFailedEmails()
// Use the shared email processing logic
await processAllEmails(payload)
console.log('✅ Email queue processing completed successfully')
} catch (error) {
@@ -49,10 +47,10 @@ export const processEmailsTask = {
throw new Error('Mailing plugin not properly initialized')
}
// Use the existing mailing service from context
// Use the task handler
await processEmailsTaskHandler(
job as { data: ProcessEmailsTaskData },
{ req, mailingService: mailingContext.service }
{ req }
)
return {

View File

@@ -1,5 +1,6 @@
import { sendEmail } from '../sendEmail.js'
import { BaseEmailDocument } from '../types/index.js'
import { processEmailById } from '../utils/emailProcessor.js'
export interface SendEmailTaskInput {
// Template mode fields
@@ -20,6 +21,7 @@ export interface SendEmailTaskInput {
replyTo?: string
scheduledAt?: string | Date // ISO date string or Date object
priority?: number
processImmediately?: boolean // If true, process the email immediately instead of waiting for the queue
// Allow any additional fields that users might have in their email collection
[key: string]: any
@@ -44,8 +46,8 @@ function transformTaskInputToSendEmailOptions(taskInput: SendEmailTaskInput) {
// Standard email fields that should be copied to data
const standardFields = ['to', 'cc', 'bcc', 'from', 'fromName', 'replyTo', 'subject', 'html', 'text', 'scheduledAt', 'priority']
// Template-specific fields that should not be copied to data
const templateFields = ['templateSlug', 'variables']
// Fields that should not be copied to data
const excludedFields = ['templateSlug', 'variables', 'processImmediately']
// Copy standard fields to data
standardFields.forEach(field => {
@@ -54,9 +56,9 @@ function transformTaskInputToSendEmailOptions(taskInput: SendEmailTaskInput) {
}
})
// Copy any additional custom fields that aren't template or standard fields
// Copy any additional custom fields that aren't excluded or standard fields
Object.keys(taskInput).forEach(key => {
if (!templateFields.includes(key) && !standardFields.includes(key)) {
if (!excludedFields.includes(key) && !standardFields.includes(key)) {
sendEmailOptions.data[key] = taskInput[key]
}
})
@@ -72,6 +74,15 @@ export const sendEmailJob = {
slug: 'send-email',
label: 'Send Email',
inputSchema: [
{
name: 'processImmediately',
type: 'checkbox' as const,
label: 'Process Immediately',
defaultValue: false,
admin: {
description: 'Process and send the email immediately instead of waiting for the queue processor'
}
},
{
name: 'templateSlug',
type: 'text' as const,
@@ -171,7 +182,8 @@ export const sendEmailJob = {
type: 'date' as const,
label: 'Schedule For',
admin: {
description: 'Optional date/time to schedule email for future delivery'
description: 'Optional date/time to schedule email for future delivery',
condition: (data: any) => !data.processImmediately
}
},
{
@@ -195,6 +207,7 @@ export const sendEmailJob = {
handler: async ({ input, payload }: any) => {
// Cast input to our expected type
const taskInput = input as SendEmailTaskInput
const shouldProcessImmediately = taskInput.processImmediately || false
try {
// Transform task input into sendEmail options using helper function
@@ -203,22 +216,36 @@ export const sendEmailJob = {
// Use the sendEmail helper to create the email
const email = await sendEmail<BaseEmailDocument>(payload, sendEmailOptions)
// If processImmediately is true, process the email now
if (shouldProcessImmediately) {
console.log(`⚡ Processing email ${email.id} immediately...`)
await processEmailById(payload, String(email.id))
console.log(`✅ Email ${email.id} processed and sent immediately`)
return {
output: {
success: true,
id: email.id,
status: 'sent',
processedImmediately: true
}
}
}
return {
output: {
success: true,
id: email.id,
status: 'queued',
processedImmediately: false
}
}
} catch (error) {
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
throw new Error(`Failed to process email: ${error.message}`, { cause: error })
} else {
throw new Error(`Failed to queue email: ${String(error)}`)
throw new Error(`Failed to process email: ${String(error)}`)
}
}
}