mirror of
https://github.com/xtr-dev/payload-mailing.git
synced 2025-12-10 08:13:23 +00:00
Fix critical type safety and validation issues
Issue 2 - Type Safety: - Remove dangerous 'as any' casts in sendEmail function - Use proper typing for payload.create() calls - Maintain type safety throughout email creation process Issue 3 - Email Validation: - Implement RFC 5322 compliant email regex - Add comprehensive validation for common invalid patterns - Check for consecutive dots, invalid domain formats - Prevent emails like 'test@.com' and 'test@domain.' Issue 4 - Error Message Logic: - Add contextual error messages for template vs direct email modes - Distinguish between template rendering failures and missing direct email content - Provide clearer guidance to developers on what went wrong Additional fixes: - Update imports to use generated Email type instead of BaseEmailData - Maintain compatibility with updated sendEmail interface 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -16,7 +16,7 @@ export { mailingJobs, sendEmailJob } from './jobs/index.js'
|
|||||||
export type { SendEmailTaskInput } from './jobs/sendEmailTask.js'
|
export type { SendEmailTaskInput } from './jobs/sendEmailTask.js'
|
||||||
|
|
||||||
// Main email sending function
|
// Main email sending function
|
||||||
export { sendEmail, type BaseEmailData, type SendEmailOptions } from './sendEmail.js'
|
export { sendEmail, type SendEmailOptions } from './sendEmail.js'
|
||||||
export { default as sendEmailDefault } from './sendEmail.js'
|
export { default as sendEmailDefault } from './sendEmail.js'
|
||||||
|
|
||||||
// Utility functions for developers
|
// Utility functions for developers
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { sendEmail, type BaseEmailData } from '../sendEmail.js'
|
import { sendEmail } from '../sendEmail.js'
|
||||||
|
import { Email } from '../payload-types.js'
|
||||||
|
|
||||||
export interface SendEmailTaskInput {
|
export interface SendEmailTaskInput {
|
||||||
// Template mode fields
|
// Template mode fields
|
||||||
@@ -153,7 +154,7 @@ export const sendEmailJob = {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Use the sendEmail helper to create the email
|
// Use the sendEmail helper to create the email
|
||||||
const email = await sendEmail<BaseEmailData>(payload, sendEmailOptions)
|
const email = await sendEmail<Email>(payload, sendEmailOptions)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
output: {
|
output: {
|
||||||
|
|||||||
@@ -66,8 +66,17 @@ export const sendEmail = async <T extends Email = Email>(
|
|||||||
throw new Error('Field "to" is required for sending emails')
|
throw new Error('Field "to" is required for sending emails')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!emailData.subject || !emailData.html) {
|
// Validate required fields based on whether template was used
|
||||||
throw new Error('Fields "subject" and "html" are required when not using a template')
|
if (options.template) {
|
||||||
|
// When using template, subject and html should have been set by renderTemplate
|
||||||
|
if (!emailData.subject || !emailData.html) {
|
||||||
|
throw new Error(`Template rendering failed: template "${options.template.slug}" did not provide required subject and html content`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// When not using template, user must provide subject and html directly
|
||||||
|
if (!emailData.subject || !emailData.html) {
|
||||||
|
throw new Error('Fields "subject" and "html" are required when sending direct emails without a template')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process email addresses using shared validation (handle null values)
|
// Process email addresses using shared validation (handle null values)
|
||||||
@@ -81,13 +90,13 @@ export const sendEmail = async <T extends Email = Email>(
|
|||||||
emailData.bcc = parseAndValidateEmails(emailData.bcc as string | string[])
|
emailData.bcc = parseAndValidateEmails(emailData.bcc as string | string[])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the email in the collection
|
// Create the email in the collection with proper typing
|
||||||
const email = await payload.create({
|
const email = await payload.create({
|
||||||
collection: collectionSlug as any,
|
collection: collectionSlug,
|
||||||
data: emailData as any
|
data: emailData
|
||||||
})
|
})
|
||||||
|
|
||||||
return email as unknown as T
|
return email as T
|
||||||
}
|
}
|
||||||
|
|
||||||
export default sendEmail
|
export default sendEmail
|
||||||
|
|||||||
@@ -15,9 +15,20 @@ export const parseAndValidateEmails = (emails: string | string[] | null | undefi
|
|||||||
emailList = emails.split(',').map(email => email.trim()).filter(Boolean)
|
emailList = emails.split(',').map(email => email.trim()).filter(Boolean)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Basic email validation
|
// RFC 5322 compliant email validation
|
||||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
|
||||||
const invalidEmails = emailList.filter(email => !emailRegex.test(email))
|
const invalidEmails = emailList.filter(email => {
|
||||||
|
// Check basic format
|
||||||
|
if (!emailRegex.test(email)) return true
|
||||||
|
// Check for common invalid patterns
|
||||||
|
if (email.includes('..') || email.startsWith('.') || email.endsWith('.')) return true
|
||||||
|
if (email.includes('@.') || email.includes('.@')) return true
|
||||||
|
// Check domain has at least one dot
|
||||||
|
const parts = email.split('@')
|
||||||
|
if (parts.length !== 2 || !parts[1].includes('.')) return true
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
if (invalidEmails.length > 0) {
|
if (invalidEmails.length > 0) {
|
||||||
throw new Error(`Invalid email addresses: ${invalidEmails.join(', ')}`)
|
throw new Error(`Invalid email addresses: ${invalidEmails.join(', ')}`)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user