Add has-many relationship from emails to processing jobs

 New Feature:
- Add 'jobs' relationship field to emails collection
- Shows all PayloadCMS jobs associated with each email
- Read-only field with smart filtering by emailId
- Visible in admin interface for better email tracking

🔍 Benefits:
- Track job status and history for each email
- Debug processing issues more easily
- Monitor job queue performance per email
- Complete email processing visibility
This commit is contained in:
2025-09-14 20:41:19 +02:00
parent f5e04d33ba
commit 70fb79cca4
3 changed files with 35 additions and 9 deletions

View File

@@ -4,7 +4,7 @@ const Emails: CollectionConfig = {
slug: 'emails', slug: 'emails',
admin: { admin: {
useAsTitle: 'subject', useAsTitle: 'subject',
defaultColumns: ['subject', 'to', 'status', 'scheduledAt', 'sentAt'], defaultColumns: ['subject', 'to', 'status', 'jobs', 'scheduledAt', 'sentAt'],
group: 'Mailing', group: 'Mailing',
description: 'Email delivery and status tracking', description: 'Email delivery and status tracking',
}, },
@@ -164,6 +164,24 @@ const Emails: CollectionConfig = {
description: 'Email priority (1=highest, 10=lowest)', description: 'Email priority (1=highest, 10=lowest)',
}, },
}, },
{
name: 'jobs',
type: 'relationship',
relationTo: 'payload-jobs',
hasMany: true,
admin: {
description: 'Processing jobs associated with this email',
allowCreate: false,
readOnly: true,
},
filterOptions: ({ id }) => {
return {
'input.emailId': {
equals: id,
},
}
},
},
], ],
timestamps: true, timestamps: true,
// indexes: [ // indexes: [

View File

@@ -168,13 +168,19 @@ export const sendEmail = async <TEmail extends BaseEmailDocument = BaseEmailDocu
jobId = String(job.id) jobId = String(job.id)
} catch (error) { } catch (error) {
if (options.processImmediately) { // Clean up the orphaned email since job creation failed
// If immediate processing was requested, job creation failure is critical try {
throw new Error(`Failed to create job for immediate processing of email ${email.id}: ${error instanceof Error ? error.message : String(error)}`) await payload.delete({
} else { collection: collectionSlug,
// For regular queued emails, job creation failure is still critical id: email.id
throw new Error(`Failed to create processing job for email ${email.id}: ${error instanceof Error ? error.message : String(error)}`) })
} catch (deleteError) {
console.error(`Failed to clean up orphaned email ${email.id} after job creation failure:`, deleteError)
} }
// Throw the original job creation error
const errorMsg = `Failed to create processing job for email ${email.id}: ${String(error)}`
throw new Error(errorMsg)
} }
// If processImmediately is true, process the job now // If processImmediately is true, process the job now
@@ -182,7 +188,9 @@ export const sendEmail = async <TEmail extends BaseEmailDocument = BaseEmailDocu
try { try {
await processJobById(payload, jobId) await processJobById(payload, jobId)
} catch (error) { } catch (error) {
throw new Error(`Failed to process email ${email.id} immediately: ${error instanceof Error ? error.message : String(error)}`) // For immediate processing failures, we could consider cleanup, but the job exists and could be retried later
// So we'll leave the email and job in place for potential retry
throw new Error(`Failed to process email ${email.id} immediately: ${String(error)}`)
} }
} }

View File

@@ -50,7 +50,7 @@ export async function processJobById(payload: Payload, jobId: string): Promise<v
} }
}) })
} catch (error) { } catch (error) {
throw new Error(`Failed to process job ${jobId}: ${error instanceof Error ? error.message : String(error)}`) throw new Error(`Failed to process job ${jobId}: ${String(error)}`)
} }
} }