mirror of
https://github.com/xtr-dev/payload-mailing.git
synced 2025-12-10 08:13:23 +00:00
Improve type safety, error handling, and code maintainability
- Simplify sendEmail generic constraints for better type safety - Add validation before type assertions in sendEmail - Preserve error stack traces in sendEmailTask error handling - Extract field copying logic into reusable helper function - Improve code documentation and separation of concerns 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,45 @@ export interface SendEmailTaskInput {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms task input into sendEmail options by separating template and data fields
|
||||
*/
|
||||
function transformTaskInputToSendEmailOptions(taskInput: SendEmailTaskInput) {
|
||||
const sendEmailOptions: any = {
|
||||
data: {}
|
||||
}
|
||||
|
||||
// If using template mode, set template options
|
||||
if (taskInput.templateSlug) {
|
||||
sendEmailOptions.template = {
|
||||
slug: taskInput.templateSlug,
|
||||
variables: taskInput.variables || {}
|
||||
}
|
||||
}
|
||||
|
||||
// Standard email fields that should be copied to data
|
||||
const standardFields = ['to', 'cc', 'bcc', 'subject', 'html', 'text', 'scheduledAt', 'priority']
|
||||
|
||||
// Template-specific fields that should not be copied to data
|
||||
const templateFields = ['templateSlug', 'variables']
|
||||
|
||||
// Copy standard fields to data
|
||||
standardFields.forEach(field => {
|
||||
if (taskInput[field] !== undefined) {
|
||||
sendEmailOptions.data[field] = taskInput[field]
|
||||
}
|
||||
})
|
||||
|
||||
// Copy any additional custom fields that aren't template or standard fields
|
||||
Object.keys(taskInput).forEach(key => {
|
||||
if (!templateFields.includes(key) && !standardFields.includes(key)) {
|
||||
sendEmailOptions.data[key] = taskInput[key]
|
||||
}
|
||||
})
|
||||
|
||||
return sendEmailOptions
|
||||
}
|
||||
|
||||
export const sendEmailJob = {
|
||||
slug: 'send-email',
|
||||
label: 'Send Email',
|
||||
@@ -128,40 +167,11 @@ export const sendEmailJob = {
|
||||
const taskInput = input as SendEmailTaskInput
|
||||
|
||||
try {
|
||||
// Prepare options for sendEmail based on task input
|
||||
const sendEmailOptions: any = {
|
||||
data: {}
|
||||
}
|
||||
|
||||
// If using template mode
|
||||
if (taskInput.templateSlug) {
|
||||
sendEmailOptions.template = {
|
||||
slug: taskInput.templateSlug,
|
||||
variables: taskInput.variables || {}
|
||||
}
|
||||
}
|
||||
|
||||
// Build data object from task input
|
||||
const dataFields = ['to', 'cc', 'bcc', 'subject', 'html', 'text', 'scheduledAt', 'priority']
|
||||
const additionalFields: string[] = []
|
||||
|
||||
// Copy standard fields
|
||||
dataFields.forEach(field => {
|
||||
if (taskInput[field] !== undefined) {
|
||||
sendEmailOptions.data[field] = taskInput[field]
|
||||
}
|
||||
})
|
||||
|
||||
// Copy any additional custom fields
|
||||
Object.keys(taskInput).forEach(key => {
|
||||
if (!['templateSlug', 'variables', ...dataFields].includes(key)) {
|
||||
sendEmailOptions.data[key] = taskInput[key]
|
||||
additionalFields.push(key)
|
||||
}
|
||||
})
|
||||
// Transform task input into sendEmail options using helper function
|
||||
const sendEmailOptions = transformTaskInputToSendEmailOptions(taskInput)
|
||||
|
||||
// Use the sendEmail helper to create the email
|
||||
const email = await sendEmail<Email, EmailTemplate>(payload, sendEmailOptions)
|
||||
const email = await sendEmail<Email>(payload, sendEmailOptions)
|
||||
|
||||
return {
|
||||
output: {
|
||||
@@ -171,8 +181,15 @@ export const sendEmailJob = {
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
||||
throw new Error(`Failed to queue email: ${errorMessage}`)
|
||||
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
|
||||
} else {
|
||||
throw new Error(`Failed to queue email: ${String(error)}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import {Email, EmailTemplate} from "./payload-types.js"
|
||||
import {BaseEmail} from "./types/index.js"
|
||||
|
||||
// Options for sending emails
|
||||
export interface SendEmailOptions<T extends BaseEmail = BaseEmail> {
|
||||
export interface SendEmailOptions<T extends Email = Email> {
|
||||
// Template-based email
|
||||
template?: {
|
||||
slug: string
|
||||
@@ -36,9 +36,9 @@ export interface SendEmailOptions<T extends BaseEmail = BaseEmail> {
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export const sendEmail = async <TEmail extends Email = Email, TEmailTemplate extends EmailTemplate = EmailTemplate>(
|
||||
export const sendEmail = async <TEmail extends Email = Email>(
|
||||
payload: Payload,
|
||||
options: SendEmailOptions<BaseEmail<TEmail, TEmailTemplate>>
|
||||
options: SendEmailOptions<TEmail>
|
||||
): Promise<TEmail> => {
|
||||
const mailing = getMailing(payload)
|
||||
const collectionSlug = options.collectionSlug || mailing.collections.emails || 'emails'
|
||||
@@ -97,6 +97,11 @@ export const sendEmail = async <TEmail extends Email = Email, TEmailTemplate ext
|
||||
data: emailData
|
||||
})
|
||||
|
||||
// Validate that the created email has the expected structure
|
||||
if (!email || typeof email !== 'object' || !email.id) {
|
||||
throw new Error('Failed to create email: invalid response from database')
|
||||
}
|
||||
|
||||
return email as TEmail
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user