Refactor email types for enhanced consistency and type safety

- Replace `EmailTemplate` with `BaseEmailTemplate` for stricter type validation.
- Update `sendEmail` and `sendEmailTask` to utilize refined `BaseEmail` structure.
- Simplify type definitions in `MailingService` and related modules.
This commit is contained in:
2025-09-13 23:00:41 +02:00
parent e91ab7e54e
commit 9520ec5ed1
4 changed files with 18 additions and 28 deletions

View File

@@ -1,5 +1,5 @@
import { sendEmail } from '../sendEmail.js' import { sendEmail } from '../sendEmail.js'
import { Email } from '../payload-types.js' import {Email, EmailTemplate} from '../payload-types.js'
import {BaseEmail} from "../types/index.js" import {BaseEmail} from "../types/index.js"
export interface SendEmailTaskInput { export interface SendEmailTaskInput {
@@ -161,7 +161,7 @@ export const sendEmailJob = {
}) })
// Use the sendEmail helper to create the email // Use the sendEmail helper to create the email
const email = await sendEmail<BaseEmail>(payload, sendEmailOptions) const email = await sendEmail<Email, EmailTemplate>(payload, sendEmailOptions)
return { return {
output: { output: {

View File

@@ -1,6 +1,6 @@
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 {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
@@ -36,14 +36,14 @@ export interface SendEmailOptions<T extends BaseEmail = BaseEmail> {
* }) * })
* ``` * ```
*/ */
export const sendEmail = async <T extends BaseEmail = BaseEmail, ID = string | number>( export const sendEmail = async <TEmail extends Email = Email, TEmailTemplate extends EmailTemplate = EmailTemplate>(
payload: Payload, payload: Payload,
options: SendEmailOptions<T> options: SendEmailOptions<BaseEmail<TEmail, TEmailTemplate>>
): Promise<T & {id: ID}> => { ): 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) {
@@ -59,7 +59,7 @@ export const sendEmail = async <T extends BaseEmail = BaseEmail, ID = string | n
subject, subject,
html, html,
text, text,
} as Partial<T> } as Partial<TEmail>
} }
// Validate required fields // Validate required fields
@@ -97,7 +97,7 @@ export const sendEmail = async <T extends BaseEmail = BaseEmail, ID = string | n
data: emailData data: emailData
}) })
return email as T & {id: ID} return email as TEmail
} }
export default sendEmail export default sendEmail

View File

@@ -5,10 +5,8 @@ import {
MailingPluginConfig, MailingPluginConfig,
TemplateVariables, TemplateVariables,
MailingService as IMailingService, MailingService as IMailingService,
EmailTemplate,
QueuedEmail,
MailingTransportConfig, MailingTransportConfig,
BaseEmail BaseEmail, BaseEmailTemplate
} from '../types/index.js' } from '../types/index.js'
import { serializeRichTextToHTML, serializeRichTextToText } from '../utils/richTextSerializer.js' import { serializeRichTextToHTML, serializeRichTextToText } from '../utils/richTextSerializer.js'
@@ -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<BaseEmailTemplate | 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 BaseEmailTemplate : 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: BaseEmailTemplate, variables: Record<string, any> = {}): Promise<{ html: string; text: string }> {
if (!template.content) { if (!template.content) {
return { html: '', text: '' } return { html: '', text: '' }
} }

View File

@@ -1,9 +1,11 @@
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" import {Email, EmailTemplate} from "../payload-types.js"
export type BaseEmail<TEmail = Email, TEmailTemplate = EmailTemplate> = Omit<TEmail, 'id' | 'template'> & {template: Omit<TEmailTemplate, 'id'>} export type BaseEmail<TEmail extends Email = Email, TEmailTemplate extends EmailTemplate = EmailTemplate> = Omit<TEmail, 'id' | 'template'> & {template: Omit<TEmailTemplate, 'id'> | TEmailTemplate['id'] | undefined | null}
export type BaseEmailTemplate<TEmailTemplate extends EmailTemplate = EmailTemplate> = 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,16 +39,6 @@ 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