mirror of
https://github.com/xtr-dev/payload-mailing.git
synced 2025-12-10 08:13:23 +00:00
🚀 BREAKING: Simplify API to use Payload collections directly
- Remove complex sendEmail/scheduleEmail methods and SendEmailOptions types - Add simple renderTemplate() helper for template rendering - Users now create emails using payload.create() with full type safety - Leverage Payload's existing collection system instead of duplicating functionality - Provide comprehensive migration guide and usage examples BREAKING CHANGES: - sendEmail() and scheduleEmail() methods removed - SendEmailOptions type removed - Use payload.create() with email collection instead - Use renderTemplate() helper for template rendering Benefits: ✅ Full TypeScript support with generated Payload types ✅ Use any custom fields in your email collection ✅ Leverage Payload's validation, hooks, and access control ✅ Simpler, more consistent API ✅ Less code to maintain 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -16,8 +16,7 @@ export { default as Emails } from './collections/Emails.js'
|
||||
// Utility functions for developers
|
||||
export {
|
||||
getMailing,
|
||||
sendEmail,
|
||||
scheduleEmail,
|
||||
renderTemplate,
|
||||
processEmails,
|
||||
retryFailedEmails,
|
||||
} from './utils/helpers.js'
|
||||
@@ -3,7 +3,7 @@ import { Liquid } from 'liquidjs'
|
||||
import nodemailer, { Transporter } from 'nodemailer'
|
||||
import {
|
||||
MailingPluginConfig,
|
||||
SendEmailOptions,
|
||||
TemplateVariables,
|
||||
MailingService as IMailingService,
|
||||
EmailTemplate,
|
||||
QueuedEmail,
|
||||
@@ -107,69 +107,21 @@ export class MailingService implements IMailingService {
|
||||
}
|
||||
}
|
||||
|
||||
async sendEmail(options: SendEmailOptions): Promise<string> {
|
||||
const emailId = await this.scheduleEmail({
|
||||
...options,
|
||||
scheduledAt: new Date()
|
||||
})
|
||||
async renderTemplate(templateSlug: string, variables: TemplateVariables): Promise<{ html: string; text: string; subject: string }> {
|
||||
const template = await this.getTemplateBySlug(templateSlug)
|
||||
|
||||
await this.processEmailItem(emailId)
|
||||
|
||||
return emailId
|
||||
}
|
||||
|
||||
async scheduleEmail(options: SendEmailOptions): Promise<string> {
|
||||
let html = options.html || ''
|
||||
let text = options.text || ''
|
||||
let subject = options.subject || ''
|
||||
let templateId: string | undefined = undefined
|
||||
|
||||
if (options.templateSlug) {
|
||||
const template = await this.getTemplateBySlug(options.templateSlug)
|
||||
|
||||
if (template) {
|
||||
templateId = template.id
|
||||
const variables = options.variables || {}
|
||||
const renderedContent = await this.renderEmailTemplate(template, variables)
|
||||
html = renderedContent.html
|
||||
text = renderedContent.text
|
||||
subject = await this.renderTemplate(template.subject, variables)
|
||||
} else {
|
||||
throw new Error(`Email template not found: ${options.templateSlug}`)
|
||||
}
|
||||
if (!template) {
|
||||
throw new Error(`Email template not found: ${templateSlug}`)
|
||||
}
|
||||
|
||||
if (!subject && !options.subject) {
|
||||
throw new Error('Email subject is required')
|
||||
const emailContent = await this.renderEmailTemplate(template, variables)
|
||||
const subject = await this.renderTemplateString(template.subject, variables)
|
||||
|
||||
return {
|
||||
html: emailContent.html,
|
||||
text: emailContent.text,
|
||||
subject
|
||||
}
|
||||
|
||||
if (!html && !options.html) {
|
||||
throw new Error('Email HTML content is required')
|
||||
}
|
||||
|
||||
const queueData = {
|
||||
template: templateId,
|
||||
to: Array.isArray(options.to) ? options.to : [options.to],
|
||||
cc: options.cc ? (Array.isArray(options.cc) ? options.cc : [options.cc]) : undefined,
|
||||
bcc: options.bcc ? (Array.isArray(options.bcc) ? options.bcc : [options.bcc]) : undefined,
|
||||
from: options.from || this.getDefaultFrom(),
|
||||
replyTo: options.replyTo,
|
||||
subject: subject || options.subject,
|
||||
html,
|
||||
text,
|
||||
variables: options.variables,
|
||||
scheduledAt: options.scheduledAt?.toISOString(),
|
||||
status: 'pending' as const,
|
||||
attempts: 0,
|
||||
priority: options.priority || 5,
|
||||
}
|
||||
|
||||
const result = await this.payload.create({
|
||||
collection: this.emailsCollection as any,
|
||||
data: queueData,
|
||||
})
|
||||
|
||||
return result.id as string
|
||||
}
|
||||
|
||||
async processEmails(): Promise<void> {
|
||||
@@ -366,7 +318,7 @@ export class MailingService implements IMailingService {
|
||||
}
|
||||
}
|
||||
|
||||
private async renderTemplate(template: string, variables: Record<string, any>): Promise<string> {
|
||||
private async renderTemplateString(template: string, variables: Record<string, any>): Promise<string> {
|
||||
// Use custom template renderer if provided
|
||||
if (this.config.templateRenderer) {
|
||||
try {
|
||||
@@ -434,8 +386,8 @@ export class MailingService implements IMailingService {
|
||||
let text = serializeRichTextToText(template.content)
|
||||
|
||||
// Apply template variables to the rendered content
|
||||
html = await this.renderTemplate(html, variables)
|
||||
text = await this.renderTemplate(text, variables)
|
||||
html = await this.renderTemplateString(html, variables)
|
||||
text = await this.renderTemplateString(text, variables)
|
||||
|
||||
return { html, text }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Payload } from 'payload'
|
||||
import type { CollectionConfig, RichTextField } from 'payload'
|
||||
import type { CollectionConfig, RichTextField, TypedCollection } from 'payload'
|
||||
import { Transporter } from 'nodemailer'
|
||||
|
||||
export interface EmailObject {
|
||||
@@ -83,26 +83,15 @@ export interface QueuedEmail {
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export interface SendEmailOptions {
|
||||
templateSlug?: string
|
||||
to: string | string[]
|
||||
cc?: string | string[]
|
||||
bcc?: string | string[]
|
||||
from?: string
|
||||
replyTo?: string
|
||||
subject?: string
|
||||
html?: string
|
||||
text?: string
|
||||
variables?: Record<string, any>
|
||||
scheduledAt?: Date
|
||||
priority?: number
|
||||
// Simple helper type for template variables
|
||||
export interface TemplateVariables {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface MailingService {
|
||||
sendEmail(options: SendEmailOptions): Promise<string>
|
||||
scheduleEmail(options: SendEmailOptions): Promise<string>
|
||||
processEmails(): Promise<void>
|
||||
retryFailedEmails(): Promise<void>
|
||||
renderTemplate(templateSlug: string, variables: TemplateVariables): Promise<{ html: string; text: string; subject: string }>
|
||||
}
|
||||
|
||||
export interface MailingContext {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Payload } from 'payload'
|
||||
import { SendEmailOptions } from '../types/index.js'
|
||||
import { TemplateVariables } from '../types/index.js'
|
||||
|
||||
export const getMailing = (payload: Payload) => {
|
||||
const mailing = (payload as any).mailing
|
||||
@@ -9,14 +9,9 @@ export const getMailing = (payload: Payload) => {
|
||||
return mailing
|
||||
}
|
||||
|
||||
export const sendEmail = async (payload: Payload, options: SendEmailOptions): Promise<string> => {
|
||||
export const renderTemplate = async (payload: Payload, templateSlug: string, variables: TemplateVariables): Promise<{ html: string; text: string; subject: string }> => {
|
||||
const mailing = getMailing(payload)
|
||||
return mailing.service.sendEmail(options)
|
||||
}
|
||||
|
||||
export const scheduleEmail = async (payload: Payload, options: SendEmailOptions): Promise<string> => {
|
||||
const mailing = getMailing(payload)
|
||||
return mailing.service.scheduleEmail(options)
|
||||
return mailing.service.renderTemplate(templateSlug, variables)
|
||||
}
|
||||
|
||||
export const processEmails = async (payload: Payload): Promise<void> => {
|
||||
|
||||
Reference in New Issue
Block a user