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
|
[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 = {
|
export const sendEmailJob = {
|
||||||
slug: 'send-email',
|
slug: 'send-email',
|
||||||
label: 'Send Email',
|
label: 'Send Email',
|
||||||
@@ -128,40 +167,11 @@ export const sendEmailJob = {
|
|||||||
const taskInput = input as SendEmailTaskInput
|
const taskInput = input as SendEmailTaskInput
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Prepare options for sendEmail based on task input
|
// Transform task input into sendEmail options using helper function
|
||||||
const sendEmailOptions: any = {
|
const sendEmailOptions = transformTaskInputToSendEmailOptions(taskInput)
|
||||||
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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Use the sendEmail helper to create the email
|
// Use the sendEmail helper to create the email
|
||||||
const email = await sendEmail<Email, EmailTemplate>(payload, sendEmailOptions)
|
const email = await sendEmail<Email>(payload, sendEmailOptions)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
output: {
|
output: {
|
||||||
@@ -171,8 +181,15 @@ export const sendEmailJob = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
if (error instanceof Error) {
|
||||||
throw new Error(`Failed to queue email: ${errorMessage}`)
|
// 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"
|
import {BaseEmail} from "./types/index.js"
|
||||||
|
|
||||||
// Options for sending emails
|
// Options for sending emails
|
||||||
export interface SendEmailOptions<T extends BaseEmail = BaseEmail> {
|
export interface SendEmailOptions<T extends Email = Email> {
|
||||||
// Template-based email
|
// Template-based email
|
||||||
template?: {
|
template?: {
|
||||||
slug: string
|
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,
|
payload: Payload,
|
||||||
options: SendEmailOptions<BaseEmail<TEmail, TEmailTemplate>>
|
options: SendEmailOptions<TEmail>
|
||||||
): Promise<TEmail> => {
|
): Promise<TEmail> => {
|
||||||
const mailing = getMailing(payload)
|
const mailing = getMailing(payload)
|
||||||
const collectionSlug = options.collectionSlug || mailing.collections.emails || 'emails'
|
const collectionSlug = options.collectionSlug || mailing.collections.emails || 'emails'
|
||||||
@@ -97,6 +97,11 @@ export const sendEmail = async <TEmail extends Email = Email, TEmailTemplate ext
|
|||||||
data: emailData
|
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
|
return email as TEmail
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user