Remove email outbox collection and process job; refactor email templates with rich text support and slug generation

This commit is contained in:
2025-09-13 12:11:35 +02:00
parent ed9d979d3e
commit 3868e74770
34 changed files with 2674 additions and 374 deletions

View File

@@ -1,26 +1,57 @@
import { Config } from 'payload/config'
import { MailingPluginConfig, MailingContext } from './types'
import { MailingService } from './services/MailingService'
import { createMailingJobs } from './jobs'
import EmailTemplates from './collections/EmailTemplates'
import EmailOutbox from './collections/EmailOutbox'
import { scheduleOutboxJob } from './jobs/processOutboxJob'
import type { Config } from 'payload'
import { MailingPluginConfig, MailingContext } from './types/index.js'
import { MailingService } from './services/MailingService.js'
import { createEmailTemplatesCollection } from './collections/EmailTemplates.js'
import Emails from './collections/Emails.js'
export const mailingPlugin = (pluginConfig: MailingPluginConfig) => (config: Config): Config => {
const templatesSlug = pluginConfig.collections?.templates || 'email-templates'
const outboxSlug = pluginConfig.collections?.outbox || 'email-outbox'
const queueName = pluginConfig.queue || 'default'
// Update collection slugs if custom ones are provided
// Handle templates collection configuration
const templatesConfig = pluginConfig.collections?.templates
const templatesSlug = typeof templatesConfig === 'string' ? templatesConfig : 'email-templates'
const templatesOverrides = typeof templatesConfig === 'object' ? templatesConfig : {}
// Create base templates collection with custom editor if provided
const baseTemplatesCollection = createEmailTemplatesCollection(pluginConfig.richTextEditor)
const templatesCollection = {
...EmailTemplates,
...baseTemplatesCollection,
slug: templatesSlug,
...templatesOverrides,
// Ensure admin config is properly merged
admin: {
...baseTemplatesCollection.admin,
...templatesOverrides.admin,
},
// Ensure access config is properly merged
access: {
...baseTemplatesCollection.access,
...templatesOverrides.access,
},
}
const outboxCollection = {
...EmailOutbox,
slug: outboxSlug,
fields: EmailOutbox.fields.map(field => {
// Handle emails collection configuration
const emailsConfig = pluginConfig.collections?.emails
const emailsSlug = typeof emailsConfig === 'string' ? emailsConfig : 'emails'
const emailsOverrides = typeof emailsConfig === 'object' ? emailsConfig : {}
const emailsCollection = {
...Emails,
slug: emailsSlug,
...emailsOverrides,
// Ensure admin config is properly merged
admin: {
...Emails.admin,
...emailsOverrides.admin,
},
// Ensure access config is properly merged
access: {
...Emails.access,
...emailsOverrides.access,
},
// Update relationship fields to point to correct templates collection
fields: (emailsOverrides.fields || Emails.fields).map((field: any) => {
if (field.name === 'template' && field.type === 'relationship') {
return {
...field,
@@ -36,63 +67,77 @@ export const mailingPlugin = (pluginConfig: MailingPluginConfig) => (config: Con
collections: [
...(config.collections || []),
templatesCollection,
outboxCollection,
emailsCollection,
],
jobs: {
...(config.jobs || {}),
tasks: [
...(config.jobs?.tasks || []),
// Jobs will be added via onInit hook
{
slug: 'process-email-queue',
handler: async ({ job, req }: { job: any; req: any }) => {
try {
const mailingService = new MailingService((req as any).payload, pluginConfig)
console.log('🔄 Processing email queue (pending + failed emails)...')
// Process pending emails first
await mailingService.processEmails()
// Then retry failed emails
await mailingService.retryFailedEmails()
return {
output: {
success: true,
message: 'Email queue processed successfully (pending and failed emails)'
}
}
} catch (error) {
console.error('❌ Error processing email queue:', error)
return {
output: {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
}
}
}
},
interfaceName: 'ProcessEmailQueueJob',
},
],
},
onInit: async (payload) => {
// Call original onInit if it exists
if (config.onInit) {
onInit: async (payload: any) => {
if (pluginConfig.initOrder === 'after' && config.onInit) {
await config.onInit(payload)
}
// Initialize mailing service
const mailingService = new MailingService(payload, pluginConfig)
// Add mailing jobs
const mailingJobs = createMailingJobs(mailingService)
if (payload.jobs) {
mailingJobs.forEach(job => {
payload.jobs.addTask(job)
})
}
// Schedule periodic outbox processing (every 5 minutes)
const schedulePeriodicJob = async () => {
await scheduleOutboxJob(payload, queueName, 'process-outbox', 5 * 60 * 1000) // 5 minutes
setTimeout(schedulePeriodicJob, 5 * 60 * 1000) // Schedule next run
}
// Schedule periodic retry job (every 30 minutes)
const scheduleRetryJob = async () => {
await scheduleOutboxJob(payload, queueName, 'retry-failed', 30 * 60 * 1000) // 30 minutes
setTimeout(scheduleRetryJob, 30 * 60 * 1000) // Schedule next run
}
// Start periodic jobs if jobs are enabled
if (payload.jobs) {
setTimeout(schedulePeriodicJob, 5 * 60 * 1000) // Start after 5 minutes
setTimeout(scheduleRetryJob, 15 * 60 * 1000) // Start after 15 minutes
}
// Add mailing context to payload for developer access
;(payload as any).mailing = {
payload,
service: mailingService,
config: pluginConfig,
collections: {
templates: templatesSlug,
outbox: outboxSlug,
emails: emailsSlug,
},
} as MailingContext
console.log('PayloadCMS Mailing Plugin initialized successfully')
// Call onReady callback if provided
if (pluginConfig.onReady) {
await pluginConfig.onReady(payload)
}
if (pluginConfig.initOrder !== 'after' && config.onInit) {
await config.onInit(payload)
}
},
}
}
export default mailingPlugin
export default mailingPlugin