mirror of
https://github.com/xtr-dev/payload-mailing.git
synced 2025-12-10 08:13:23 +00:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f04275d39 | ||
| 20afe30e88 | |||
| 02b3fecadf | |||
|
|
ea87f14308 | ||
| 6886027727 | |||
| 965569be06 | |||
|
|
ff788c1ecf | ||
| c12438aaa2 | |||
| 4dcbc1446a | |||
|
|
72f3d7f66d | ||
| ecc0b0a73e | |||
| a959673fc1 | |||
| 8809db6aff | |||
|
|
5905f732de | ||
| 4c495a72b0 | |||
| 8518c716e8 | |||
| 570190be01 | |||
|
|
685875d1b9 | ||
| 79044b7bc3 | |||
| e7304fe1a2 | |||
| 790eedfee7 | |||
| 9520ec5ed1 | |||
|
|
768b70a003 | ||
| e91ab7e54e | |||
| 06f9c2cb5b | |||
|
|
21b22a033a | ||
| 6ad90874cf |
95
CUSTOM-TYPES.md
Normal file
95
CUSTOM-TYPES.md
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
# Using Custom ID Types
|
||||||
|
|
||||||
|
The mailing plugin now supports both `string` and `number` ID types. By default, it works with the generic `BaseEmailDocument` interface, but you can provide your own types for full type safety.
|
||||||
|
|
||||||
|
## Usage with Your Generated Types
|
||||||
|
|
||||||
|
When you have your own generated Payload types (e.g., from `payload generate:types`), you can use them with the mailing plugin:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { sendEmail, BaseEmailDocument } from '@xtr-dev/payload-mailing'
|
||||||
|
import { Email } from './payload-types' // Your generated types
|
||||||
|
|
||||||
|
// Option 1: Use your specific Email type
|
||||||
|
const email = await sendEmail<Email>(payload, {
|
||||||
|
template: {
|
||||||
|
slug: 'welcome',
|
||||||
|
variables: { name: 'John' }
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
to: 'user@example.com',
|
||||||
|
// All your custom fields are now type-safe
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Option 2: Extend BaseEmailDocument for custom fields
|
||||||
|
interface MyEmail extends BaseEmailDocument {
|
||||||
|
customField: string
|
||||||
|
anotherField?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const customEmail = await sendEmail<MyEmail>(payload, {
|
||||||
|
data: {
|
||||||
|
to: 'user@example.com',
|
||||||
|
subject: 'Hello',
|
||||||
|
html: '<p>Hello World</p>',
|
||||||
|
customField: 'my value', // Type-safe!
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Compatibility
|
||||||
|
|
||||||
|
The plugin works with:
|
||||||
|
- **String IDs**: `id: string`
|
||||||
|
- **Number IDs**: `id: number`
|
||||||
|
- **Nullable fields**: Fields can be `null`, `undefined`, or have values
|
||||||
|
- **Date fields**: Timestamp fields support both `Date` objects and `string` (ISO) formats
|
||||||
|
- **JSON variables**: Variables field supports any JSON-compatible value type
|
||||||
|
- **Generated types**: Works with `payload generate:types` output
|
||||||
|
|
||||||
|
Your Payload configuration determines which types are used. The plugin automatically adapts to your setup.
|
||||||
|
|
||||||
|
## Type Definitions
|
||||||
|
|
||||||
|
The base interfaces provided by the plugin:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// JSON value type that matches Payload's JSON field type
|
||||||
|
type JSONValue = string | number | boolean | { [k: string]: unknown } | unknown[] | null | undefined
|
||||||
|
|
||||||
|
interface BaseEmailDocument {
|
||||||
|
id: string | number
|
||||||
|
template?: any
|
||||||
|
to: string[]
|
||||||
|
cc?: string[] | null
|
||||||
|
bcc?: string[] | null
|
||||||
|
from?: string | null
|
||||||
|
replyTo?: string | null
|
||||||
|
subject: string
|
||||||
|
html: string
|
||||||
|
text?: string | null
|
||||||
|
variables?: JSONValue // Supports any JSON-compatible value
|
||||||
|
scheduledAt?: string | Date | null
|
||||||
|
sentAt?: string | Date | null
|
||||||
|
status?: 'pending' | 'processing' | 'sent' | 'failed' | null
|
||||||
|
attempts?: number | null
|
||||||
|
lastAttemptAt?: string | Date | null
|
||||||
|
error?: string | null
|
||||||
|
priority?: number | null
|
||||||
|
createdAt?: string | Date | null
|
||||||
|
updatedAt?: string | Date | null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BaseEmailTemplateDocument {
|
||||||
|
id: string | number
|
||||||
|
name: string
|
||||||
|
slug: string
|
||||||
|
subject?: string | null
|
||||||
|
content?: any
|
||||||
|
createdAt?: string | Date | null
|
||||||
|
updatedAt?: string | Date | null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
These provide a foundation that works with any ID type while maintaining type safety for the core email functionality.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@xtr-dev/payload-mailing",
|
"name": "@xtr-dev/payload-mailing",
|
||||||
"version": "0.1.9",
|
"version": "0.1.17",
|
||||||
"description": "Template-based email system with scheduling and job processing for PayloadCMS",
|
"description": "Template-based email system with scheduling and job processing for PayloadCMS",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { sendEmail } from '../sendEmail.js'
|
import { sendEmail } from '../sendEmail.js'
|
||||||
import { Email } from '../payload-types.js'
|
import { BaseEmailDocument } from '../types/index.js'
|
||||||
|
|
||||||
export interface SendEmailTaskInput {
|
export interface SendEmailTaskInput {
|
||||||
// Template mode fields
|
// Template mode fields
|
||||||
@@ -15,13 +15,52 @@ export interface SendEmailTaskInput {
|
|||||||
to: string | string[]
|
to: string | string[]
|
||||||
cc?: string | string[]
|
cc?: string | string[]
|
||||||
bcc?: string | string[]
|
bcc?: string | string[]
|
||||||
scheduledAt?: string // ISO date string
|
scheduledAt?: string | Date // ISO date string or Date object
|
||||||
priority?: number
|
priority?: number
|
||||||
|
|
||||||
// Allow any additional fields that users might have in their email collection
|
// Allow any additional fields that users might have in their email collection
|
||||||
[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',
|
||||||
@@ -116,64 +155,42 @@ export const sendEmailJob = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
outputSchema: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text' as const
|
||||||
|
}
|
||||||
|
],
|
||||||
handler: async ({ input, payload }: any) => {
|
handler: async ({ input, payload }: any) => {
|
||||||
// Cast input to our expected type
|
// Cast input to our expected type
|
||||||
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>(payload, sendEmailOptions)
|
const email = await sendEmail<BaseEmailDocument>(payload, sendEmailOptions)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
output: {
|
output: {
|
||||||
success: true,
|
success: true,
|
||||||
emailId: email.id,
|
id: email.id,
|
||||||
message: `Email queued successfully with ID: ${email.id}`,
|
|
||||||
mode: taskInput.templateSlug ? 'template' : 'direct',
|
|
||||||
templateSlug: taskInput.templateSlug || null,
|
|
||||||
subject: email.subject,
|
|
||||||
recipients: Array.isArray(email.to) ? email.to.length : 1,
|
|
||||||
scheduledAt: email.scheduledAt || null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} 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)}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default sendEmailJob
|
export default sendEmailJob
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Payload } from 'payload'
|
import { Payload } from 'payload'
|
||||||
import { getMailing, renderTemplate, parseAndValidateEmails } from './utils/helpers.js'
|
import { getMailing, renderTemplate, parseAndValidateEmails } from './utils/helpers.js'
|
||||||
import {Email} from "./payload-types.js"
|
import { BaseEmailDocument } from './types/index.js'
|
||||||
|
|
||||||
// Options for sending emails
|
// Options for sending emails
|
||||||
export interface SendEmailOptions<T extends Email = Email> {
|
export interface SendEmailOptions<T extends BaseEmailDocument = BaseEmailDocument> {
|
||||||
// Template-based email
|
// Template-based email
|
||||||
template?: {
|
template?: {
|
||||||
slug: string
|
slug: string
|
||||||
@@ -35,14 +35,14 @@ export interface SendEmailOptions<T extends Email = Email> {
|
|||||||
* })
|
* })
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export const sendEmail = async <T extends Email = Email>(
|
export const sendEmail = async <TEmail extends BaseEmailDocument = BaseEmailDocument>(
|
||||||
payload: Payload,
|
payload: Payload,
|
||||||
options: SendEmailOptions<T>
|
options: SendEmailOptions<TEmail>
|
||||||
): Promise<T> => {
|
): 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'
|
||||||
|
|
||||||
let emailData: Partial<T> = { ...options.data } as Partial<T>
|
let emailData: Partial<TEmail> = { ...options.data } as Partial<TEmail>
|
||||||
|
|
||||||
// If using a template, render it first
|
// If using a template, render it first
|
||||||
if (options.template) {
|
if (options.template) {
|
||||||
@@ -58,7 +58,7 @@ export const sendEmail = async <T extends Email = Email>(
|
|||||||
subject,
|
subject,
|
||||||
html,
|
html,
|
||||||
text,
|
text,
|
||||||
} as Partial<T>
|
} as Partial<TEmail>
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate required fields
|
// Validate required fields
|
||||||
@@ -83,12 +83,39 @@ export const sendEmail = async <T extends Email = Email>(
|
|||||||
if (emailData.to) {
|
if (emailData.to) {
|
||||||
emailData.to = parseAndValidateEmails(emailData.to as string | string[])
|
emailData.to = parseAndValidateEmails(emailData.to as string | string[])
|
||||||
}
|
}
|
||||||
if (emailData.cc) {
|
if (emailData.cc && emailData.cc !== null) {
|
||||||
emailData.cc = parseAndValidateEmails(emailData.cc as string | string[])
|
emailData.cc = parseAndValidateEmails(emailData.cc as string | string[])
|
||||||
}
|
}
|
||||||
if (emailData.bcc) {
|
if (emailData.bcc && emailData.bcc !== null) {
|
||||||
emailData.bcc = parseAndValidateEmails(emailData.bcc as string | string[])
|
emailData.bcc = parseAndValidateEmails(emailData.bcc as string | string[])
|
||||||
}
|
}
|
||||||
|
if (emailData.replyTo && emailData.replyTo !== null) {
|
||||||
|
const validated = parseAndValidateEmails(emailData.replyTo as string | string[])
|
||||||
|
// replyTo should be a single email, so take the first one if array
|
||||||
|
emailData.replyTo = validated && validated.length > 0 ? validated[0] : undefined
|
||||||
|
}
|
||||||
|
if (emailData.from && emailData.from !== null) {
|
||||||
|
const validated = parseAndValidateEmails(emailData.from as string | string[])
|
||||||
|
// from should be a single email, so take the first one if array
|
||||||
|
emailData.from = validated && validated.length > 0 ? validated[0] : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize Date objects to ISO strings for consistent database storage
|
||||||
|
if (emailData.scheduledAt instanceof Date) {
|
||||||
|
emailData.scheduledAt = emailData.scheduledAt.toISOString()
|
||||||
|
}
|
||||||
|
if (emailData.sentAt instanceof Date) {
|
||||||
|
emailData.sentAt = emailData.sentAt.toISOString()
|
||||||
|
}
|
||||||
|
if (emailData.lastAttemptAt instanceof Date) {
|
||||||
|
emailData.lastAttemptAt = emailData.lastAttemptAt.toISOString()
|
||||||
|
}
|
||||||
|
if (emailData.createdAt instanceof Date) {
|
||||||
|
emailData.createdAt = emailData.createdAt.toISOString()
|
||||||
|
}
|
||||||
|
if (emailData.updatedAt instanceof Date) {
|
||||||
|
emailData.updatedAt = emailData.updatedAt.toISOString()
|
||||||
|
}
|
||||||
|
|
||||||
// Create the email in the collection with proper typing
|
// Create the email in the collection with proper typing
|
||||||
const email = await payload.create({
|
const email = await payload.create({
|
||||||
@@ -96,7 +123,12 @@ export const sendEmail = async <T extends Email = Email>(
|
|||||||
data: emailData
|
data: emailData
|
||||||
})
|
})
|
||||||
|
|
||||||
return email as T
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
export default sendEmail
|
export default sendEmail
|
||||||
|
|||||||
@@ -5,10 +5,8 @@ import {
|
|||||||
MailingPluginConfig,
|
MailingPluginConfig,
|
||||||
TemplateVariables,
|
TemplateVariables,
|
||||||
MailingService as IMailingService,
|
MailingService as IMailingService,
|
||||||
EmailTemplate,
|
|
||||||
QueuedEmail,
|
|
||||||
MailingTransportConfig,
|
MailingTransportConfig,
|
||||||
BaseEmail
|
BaseEmail, BaseEmailTemplate, BaseEmailDocument, BaseEmailTemplateDocument
|
||||||
} from '../types/index.js'
|
} from '../types/index.js'
|
||||||
import { serializeRichTextToHTML, serializeRichTextToText } from '../utils/richTextSerializer.js'
|
import { serializeRichTextToHTML, serializeRichTextToText } from '../utils/richTextSerializer.js'
|
||||||
|
|
||||||
@@ -133,7 +131,7 @@ export class MailingService implements IMailingService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const emailContent = await this.renderEmailTemplate(template, variables)
|
const emailContent = await this.renderEmailTemplate(template, variables)
|
||||||
const subject = await this.renderTemplateString(template.subject, variables)
|
const subject = await this.renderTemplateString(template.subject || '', variables)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
html: emailContent.html,
|
html: emailContent.html,
|
||||||
@@ -238,7 +236,7 @@ export class MailingService implements IMailingService {
|
|||||||
const email = await this.payload.findByID({
|
const email = await this.payload.findByID({
|
||||||
collection: this.emailsCollection as any,
|
collection: this.emailsCollection as any,
|
||||||
id: emailId,
|
id: emailId,
|
||||||
}) as BaseEmail
|
}) as BaseEmailDocument
|
||||||
|
|
||||||
const mailOptions = {
|
const mailOptions = {
|
||||||
from: email.from,
|
from: email.from,
|
||||||
@@ -287,7 +285,7 @@ export class MailingService implements IMailingService {
|
|||||||
const email = await this.payload.findByID({
|
const email = await this.payload.findByID({
|
||||||
collection: this.emailsCollection as any,
|
collection: this.emailsCollection as any,
|
||||||
id: emailId,
|
id: emailId,
|
||||||
}) as QueuedEmail
|
}) as BaseEmail
|
||||||
|
|
||||||
const newAttempts = (email.attempts || 0) + 1
|
const newAttempts = (email.attempts || 0) + 1
|
||||||
|
|
||||||
@@ -302,7 +300,7 @@ export class MailingService implements IMailingService {
|
|||||||
return newAttempts
|
return newAttempts
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getTemplateBySlug(templateSlug: string): Promise<EmailTemplate | null> {
|
private async getTemplateBySlug(templateSlug: string): Promise<BaseEmailTemplateDocument | null> {
|
||||||
try {
|
try {
|
||||||
const { docs } = await this.payload.find({
|
const { docs } = await this.payload.find({
|
||||||
collection: this.templatesCollection as any,
|
collection: this.templatesCollection as any,
|
||||||
@@ -314,7 +312,7 @@ export class MailingService implements IMailingService {
|
|||||||
limit: 1,
|
limit: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
return docs.length > 0 ? docs[0] as EmailTemplate : null
|
return docs.length > 0 ? docs[0] as BaseEmailTemplateDocument : null
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Template with slug '${templateSlug}' not found:`, error)
|
console.error(`Template with slug '${templateSlug}' not found:`, error)
|
||||||
return null
|
return null
|
||||||
@@ -379,7 +377,7 @@ export class MailingService implements IMailingService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private async renderEmailTemplate(template: EmailTemplate, variables: Record<string, any> = {}): Promise<{ html: string; text: string }> {
|
private async renderEmailTemplate(template: BaseEmailTemplateDocument, variables: Record<string, any> = {}): Promise<{ html: string; text: string }> {
|
||||||
if (!template.content) {
|
if (!template.content) {
|
||||||
return { html: '', text: '' }
|
return { html: '', text: '' }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,47 @@
|
|||||||
import { Payload } from 'payload'
|
import { Payload } from 'payload'
|
||||||
import type { CollectionConfig, RichTextField } from 'payload'
|
import type { CollectionConfig, RichTextField } from 'payload'
|
||||||
import { Transporter } from 'nodemailer'
|
import { Transporter } from 'nodemailer'
|
||||||
import {Email} from "../payload-types.js"
|
|
||||||
|
|
||||||
export type BaseEmail<TEmail = Email, TEmailTemplate = EmailTemplate> = Omit<TEmail, 'id' | 'template'> & {template: Omit<TEmailTemplate, 'id'>}
|
// JSON value type that matches Payload's JSON field type
|
||||||
|
export type JSONValue = string | number | boolean | { [k: string]: unknown } | unknown[] | null | undefined
|
||||||
|
|
||||||
|
// Generic base interfaces that work with any ID type and null values
|
||||||
|
export interface BaseEmailDocument {
|
||||||
|
id: string | number
|
||||||
|
template?: any
|
||||||
|
to: string[]
|
||||||
|
cc?: string[] | null
|
||||||
|
bcc?: string[] | null
|
||||||
|
from?: string | null
|
||||||
|
replyTo?: string | null
|
||||||
|
subject: string
|
||||||
|
html: string
|
||||||
|
text?: string | null
|
||||||
|
variables?: JSONValue
|
||||||
|
scheduledAt?: string | Date | null
|
||||||
|
sentAt?: string | Date | null
|
||||||
|
status?: 'pending' | 'processing' | 'sent' | 'failed' | null
|
||||||
|
attempts?: number | null
|
||||||
|
lastAttemptAt?: string | Date | null
|
||||||
|
error?: string | null
|
||||||
|
priority?: number | null
|
||||||
|
createdAt?: string | Date | null
|
||||||
|
updatedAt?: string | Date | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BaseEmailTemplateDocument {
|
||||||
|
id: string | number
|
||||||
|
name: string
|
||||||
|
slug: string
|
||||||
|
subject?: string | null
|
||||||
|
content?: any
|
||||||
|
createdAt?: string | Date | null
|
||||||
|
updatedAt?: string | Date | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BaseEmail<TEmail extends BaseEmailDocument = BaseEmailDocument, TEmailTemplate extends BaseEmailTemplateDocument = BaseEmailTemplateDocument> = Omit<TEmail, 'id' | 'template'> & {template: Omit<TEmailTemplate, 'id'> | TEmailTemplate['id'] | undefined | null}
|
||||||
|
|
||||||
|
export type BaseEmailTemplate<TEmailTemplate extends BaseEmailTemplateDocument = BaseEmailTemplateDocument> = Omit<TEmailTemplate, 'id'>
|
||||||
|
|
||||||
export type TemplateRendererHook = (template: string, variables: Record<string, any>) => string | Promise<string>
|
export type TemplateRendererHook = (template: string, variables: Record<string, any>) => string | Promise<string>
|
||||||
|
|
||||||
@@ -37,36 +75,26 @@ export interface MailingTransportConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EmailTemplate {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
slug: string
|
|
||||||
subject: string
|
|
||||||
content: any // Lexical editor state
|
|
||||||
createdAt: string
|
|
||||||
updatedAt: string
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export interface QueuedEmail {
|
export interface QueuedEmail {
|
||||||
id: string
|
id: string
|
||||||
template?: string
|
template?: string | null
|
||||||
to: string[]
|
to: string[]
|
||||||
cc?: string[]
|
cc?: string[] | null
|
||||||
bcc?: string[]
|
bcc?: string[] | null
|
||||||
from?: string
|
from?: string | null
|
||||||
replyTo?: string
|
replyTo?: string | null
|
||||||
subject: string
|
subject: string
|
||||||
html: string
|
html: string
|
||||||
text?: string
|
text?: string | null
|
||||||
variables?: Record<string, any>
|
variables?: JSONValue
|
||||||
scheduledAt?: string
|
scheduledAt?: string | Date | null
|
||||||
sentAt?: string
|
sentAt?: string | Date | null
|
||||||
status: 'pending' | 'processing' | 'sent' | 'failed'
|
status: 'pending' | 'processing' | 'sent' | 'failed'
|
||||||
attempts: number
|
attempts: number
|
||||||
lastAttemptAt?: string
|
lastAttemptAt?: string | Date | null
|
||||||
error?: string
|
error?: string | null
|
||||||
priority?: number
|
priority?: number | null
|
||||||
createdAt: string
|
createdAt: string
|
||||||
updatedAt: string
|
updatedAt: string
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user