mirror of
https://github.com/xtr-dev/payload-mailing.git
synced 2025-12-10 08:13:23 +00:00
Move sendEmail to dedicated file for better visibility
- Extract sendEmail function to src/sendEmail.ts as primary module - Export BaseEmailData and SendEmailOptions interfaces alongside - Update all imports to use new location - Add sendEmailDefault export for CommonJS compatibility - Export parseAndValidateEmails for external utility use - Updated README to highlight sendEmail as primary export Breaking change: BaseEmailData and SendEmailOptions now imported from main module, not utils/helpers 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -59,6 +59,7 @@ export default buildConfig({
|
||||
### 2. Send emails with type-safe helper
|
||||
|
||||
```typescript
|
||||
// sendEmail is a primary export for easy access
|
||||
import { sendEmail } from '@xtr-dev/payload-mailing'
|
||||
import { Email } from './payload-types' // Your generated types
|
||||
|
||||
|
||||
@@ -15,13 +15,15 @@ export { default as Emails } from './collections/Emails.js'
|
||||
export { mailingJobs, sendEmailJob } from './jobs/index.js'
|
||||
export type { SendEmailTaskInput } from './jobs/sendEmailTask.js'
|
||||
|
||||
// Main email sending function
|
||||
export { sendEmail, type BaseEmailData, type SendEmailOptions } from './sendEmail.js'
|
||||
export { default as sendEmailDefault } from './sendEmail.js'
|
||||
|
||||
// Utility functions for developers
|
||||
export {
|
||||
getMailing,
|
||||
renderTemplate,
|
||||
processEmails,
|
||||
retryFailedEmails,
|
||||
sendEmail,
|
||||
type BaseEmailData,
|
||||
type SendEmailOptions,
|
||||
parseAndValidateEmails,
|
||||
} from './utils/helpers.js'
|
||||
@@ -1,4 +1,4 @@
|
||||
import { sendEmail, type BaseEmailData } from '../utils/helpers.js'
|
||||
import { sendEmail, type BaseEmailData } from '../sendEmail.js'
|
||||
|
||||
export interface SendEmailTaskInput {
|
||||
// Template mode fields
|
||||
|
||||
110
src/sendEmail.ts
Normal file
110
src/sendEmail.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { Payload } from 'payload'
|
||||
import { getMailing, renderTemplate, parseAndValidateEmails } from './utils/helpers.js'
|
||||
|
||||
// Base type for email data that all emails must have
|
||||
export interface BaseEmailData {
|
||||
to: string | string[]
|
||||
cc?: string | string[]
|
||||
bcc?: string | string[]
|
||||
subject?: string
|
||||
html?: string
|
||||
text?: string
|
||||
scheduledAt?: string | Date
|
||||
priority?: number
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
// Options for sending emails
|
||||
export interface SendEmailOptions<T extends BaseEmailData = BaseEmailData> {
|
||||
// Template-based email
|
||||
template?: {
|
||||
slug: string
|
||||
variables?: Record<string, any>
|
||||
}
|
||||
// Direct email data
|
||||
data?: Partial<T>
|
||||
// Common options
|
||||
collectionSlug?: string // defaults to 'emails'
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an email with full type safety
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // With your generated Email type
|
||||
* import { Email } from './payload-types'
|
||||
*
|
||||
* const email = await sendEmail<Email>(payload, {
|
||||
* template: {
|
||||
* slug: 'welcome',
|
||||
* variables: { name: 'John' }
|
||||
* },
|
||||
* data: {
|
||||
* to: 'user@example.com',
|
||||
* customField: 'value' // Your custom fields are type-safe!
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export const sendEmail = async <T extends BaseEmailData = BaseEmailData>(
|
||||
payload: Payload,
|
||||
options: SendEmailOptions<T>
|
||||
): Promise<T> => {
|
||||
const mailing = getMailing(payload)
|
||||
const collectionSlug = options.collectionSlug || mailing.collections.emails || 'emails'
|
||||
|
||||
let emailData: Partial<T> = { ...options.data } as Partial<T>
|
||||
|
||||
// If using a template, render it first
|
||||
if (options.template) {
|
||||
const { html, text, subject } = await renderTemplate(
|
||||
payload,
|
||||
options.template.slug,
|
||||
options.template.variables || {}
|
||||
)
|
||||
|
||||
// Template values take precedence over data values
|
||||
emailData = {
|
||||
...emailData,
|
||||
subject,
|
||||
html,
|
||||
text,
|
||||
} as Partial<T>
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
if (!emailData.to) {
|
||||
throw new Error('Field "to" is required for sending emails')
|
||||
}
|
||||
|
||||
if (!emailData.subject || !emailData.html) {
|
||||
throw new Error('Fields "subject" and "html" are required when not using a template')
|
||||
}
|
||||
|
||||
// Process email addresses using shared validation
|
||||
if (emailData.to) {
|
||||
emailData.to = parseAndValidateEmails(emailData.to as string | string[])
|
||||
}
|
||||
if (emailData.cc) {
|
||||
emailData.cc = parseAndValidateEmails(emailData.cc as string | string[])
|
||||
}
|
||||
if (emailData.bcc) {
|
||||
emailData.bcc = parseAndValidateEmails(emailData.bcc as string | string[])
|
||||
}
|
||||
|
||||
// Convert scheduledAt to ISO string if it's a Date
|
||||
if (emailData.scheduledAt instanceof Date) {
|
||||
emailData.scheduledAt = emailData.scheduledAt.toISOString()
|
||||
}
|
||||
|
||||
// Create the email in the collection
|
||||
const email = await payload.create({
|
||||
collection: collectionSlug as any,
|
||||
data: emailData as any
|
||||
})
|
||||
|
||||
return email as unknown as T
|
||||
}
|
||||
|
||||
export default sendEmail
|
||||
@@ -1,32 +1,6 @@
|
||||
import { Payload } from 'payload'
|
||||
import { TemplateVariables } from '../types/index.js'
|
||||
|
||||
// Base type for email data that all emails must have
|
||||
export interface BaseEmailData {
|
||||
to: string | string[]
|
||||
cc?: string | string[]
|
||||
bcc?: string | string[]
|
||||
subject?: string
|
||||
html?: string
|
||||
text?: string
|
||||
scheduledAt?: string | Date
|
||||
priority?: number
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
// Options for sending emails
|
||||
export interface SendEmailOptions<T extends BaseEmailData = BaseEmailData> {
|
||||
// Template-based email
|
||||
template?: {
|
||||
slug: string
|
||||
variables?: Record<string, any>
|
||||
}
|
||||
// Direct email data
|
||||
data?: Partial<T>
|
||||
// Common options
|
||||
collectionSlug?: string // defaults to 'emails'
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and validate email addresses
|
||||
* @internal
|
||||
@@ -72,84 +46,4 @@ export const processEmails = async (payload: Payload): Promise<void> => {
|
||||
export const retryFailedEmails = async (payload: Payload): Promise<void> => {
|
||||
const mailing = getMailing(payload)
|
||||
return mailing.service.retryFailedEmails()
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an email with full type safety
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // With your generated Email type
|
||||
* import { Email } from './payload-types'
|
||||
*
|
||||
* const email = await sendEmail<Email>(payload, {
|
||||
* template: {
|
||||
* slug: 'welcome',
|
||||
* variables: { name: 'John' }
|
||||
* },
|
||||
* data: {
|
||||
* to: 'user@example.com',
|
||||
* customField: 'value' // Your custom fields are type-safe!
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export const sendEmail = async <T extends BaseEmailData = BaseEmailData>(
|
||||
payload: Payload,
|
||||
options: SendEmailOptions<T>
|
||||
): Promise<T> => {
|
||||
const mailing = getMailing(payload)
|
||||
const collectionSlug = options.collectionSlug || mailing.collections.emails || 'emails'
|
||||
|
||||
let emailData: Partial<T> = { ...options.data } as Partial<T>
|
||||
|
||||
// If using a template, render it first
|
||||
if (options.template) {
|
||||
const { html, text, subject } = await renderTemplate(
|
||||
payload,
|
||||
options.template.slug,
|
||||
options.template.variables || {}
|
||||
)
|
||||
|
||||
// Template values take precedence over data values
|
||||
emailData = {
|
||||
...emailData,
|
||||
subject,
|
||||
html,
|
||||
text,
|
||||
} as Partial<T>
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
if (!emailData.to) {
|
||||
throw new Error('Field "to" is required for sending emails')
|
||||
}
|
||||
|
||||
if (!emailData.subject || !emailData.html) {
|
||||
throw new Error('Fields "subject" and "html" are required when not using a template')
|
||||
}
|
||||
|
||||
// Process email addresses using shared validation
|
||||
if (emailData.to) {
|
||||
emailData.to = parseAndValidateEmails(emailData.to as string | string[])
|
||||
}
|
||||
if (emailData.cc) {
|
||||
emailData.cc = parseAndValidateEmails(emailData.cc as string | string[])
|
||||
}
|
||||
if (emailData.bcc) {
|
||||
emailData.bcc = parseAndValidateEmails(emailData.bcc as string | string[])
|
||||
}
|
||||
|
||||
// Convert scheduledAt to ISO string if it's a Date
|
||||
if (emailData.scheduledAt instanceof Date) {
|
||||
emailData.scheduledAt = emailData.scheduledAt.toISOString()
|
||||
}
|
||||
|
||||
// Create the email in the collection
|
||||
const email = await payload.create({
|
||||
collection: collectionSlug as any,
|
||||
data: emailData as any
|
||||
})
|
||||
|
||||
return email as unknown as T
|
||||
}
|
||||
Reference in New Issue
Block a user