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',
admin: {
useAsTitle: 'subject',
defaultColumns: ['subject', 'to', 'status', 'scheduledAt', 'sentAt'],
defaultColumns: ['subject', 'to', 'status', 'jobs', 'scheduledAt', 'sentAt'],
group: 'Mailing',
description: 'Email delivery and status tracking',
},
@@ -164,6 +164,24 @@ const Emails: CollectionConfig = {
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,
// indexes: [

View File

@@ -168,13 +168,19 @@ export const sendEmail = async <TEmail extends BaseEmailDocument = BaseEmailDocu
jobId = String(job.id)
} catch (error) {
if (options.processImmediately) {
// If immediate processing was requested, job creation failure is critical
throw new Error(`Failed to create job for immediate processing of email ${email.id}: ${error instanceof Error ? error.message : String(error)}`)
} else {
// For regular queued emails, job creation failure is still critical
throw new Error(`Failed to create processing job for email ${email.id}: ${error instanceof Error ? error.message : String(error)}`)
// Clean up the orphaned email since job creation failed
try {
await payload.delete({
collection: collectionSlug,
id: email.id
})
} 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
@@ -182,7 +188,9 @@ export const sendEmail = async <TEmail extends BaseEmailDocument = BaseEmailDocu
try {
await processJobById(payload, jobId)
} 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) {
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)}`)
}
}