Fix critical error handling and race condition issues

🔴 Critical fixes:
- Fix race condition: processImmediately now properly fails if job creation fails
- Fix silent job failures: job creation failures now throw errors instead of warnings
- Ensure atomic operations: either email + job succeed together, or both fail

⚠️ Improvements:
- Simplify error handling in processEmailJob to be more consistent
- Add proper validation for missing PayloadCMS jobs configuration
- Make error messages more descriptive and actionable
This commit is contained in:
2025-09-14 20:32:23 +02:00
parent b6ec55bc45
commit 27d504079a
2 changed files with 33 additions and 31 deletions

View File

@@ -64,13 +64,7 @@ export const processEmailJob = {
} }
} }
} catch (error) { } catch (error) {
// Re-throw Error instances to preserve stack trace and error context throw new Error(`Failed to process email ${emailId}: ${error instanceof Error ? error.message : String(error)}`)
if (error instanceof Error) {
throw error
} else {
// Only wrap non-Error values
throw new Error(`Failed to process email ${emailId}: ${String(error)}`)
}
} }
} }
} }

View File

@@ -145,36 +145,44 @@ export const sendEmail = async <TEmail extends BaseEmailDocument = BaseEmailDocu
// Create an individual job for this email // Create an individual job for this email
const queueName = options.queue || mailingConfig.queue || 'default' const queueName = options.queue || mailingConfig.queue || 'default'
let jobId: string | undefined if (!payload.jobs) {
if (options.processImmediately) {
if (payload.jobs) { throw new Error('PayloadCMS jobs not configured - cannot process email immediately')
try { } else {
const job = await payload.jobs.queue({ console.warn('PayloadCMS jobs not configured - emails will not be processed automatically')
queue: queueName, return email as TEmail
task: 'process-email',
input: {
emailId: String(email.id)
},
// If scheduled, set the waitUntil date
waitUntil: emailData.scheduledAt ? new Date(emailData.scheduledAt) : undefined
})
jobId = String(job.id)
} catch (error) {
console.warn(`Failed to create job for email ${email.id}:`, error)
// Don't fail the entire sendEmail operation if job creation fails
} }
} else {
console.warn('PayloadCMS jobs not configured - emails will not be processed automatically')
} }
// If processImmediately is true and we have a job, process it now let jobId: string
if (options.processImmediately && jobId) { try {
const job = await payload.jobs.queue({
queue: queueName,
task: 'process-email',
input: {
emailId: String(email.id)
},
// If scheduled, set the waitUntil date
waitUntil: emailData.scheduledAt ? new Date(emailData.scheduledAt) : undefined
})
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)}`)
}
}
// If processImmediately is true, process the job now
if (options.processImmediately) {
try { try {
await processJobById(payload, jobId) await processJobById(payload, jobId)
} catch (error) { } catch (error) {
console.warn(`Failed to process email ${email.id} immediately:`, error) throw new Error(`Failed to process email ${email.id} immediately: ${error instanceof Error ? error.message : String(error)}`)
// Don't fail the entire sendEmail operation if immediate processing fails
} }
} }