From 570190be018ed39e1bad3c22a3873a8f2c672f78 Mon Sep 17 00:00:00 2001 From: Bas van den Aakster Date: Sat, 13 Sep 2025 23:18:37 +0200 Subject: [PATCH 1/3] Support custom ID types (string/number) for improved compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace hardcoded payload-types imports with generic BaseEmailDocument interface - Update sendEmail and sendEmailTask to work with both string and number IDs - Refactor MailingService to use generic document types instead of specific ones - Add BaseEmailDocument and BaseEmailTemplateDocument interfaces supporting id: string | number - Export BaseEmailDocument for users to extend with their custom fields - Fix TypeScript compilation error in template subject handling - Add CUSTOM-TYPES.md documentation for users with different ID types Fixes compatibility issue where plugin required number IDs but user projects used string IDs. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CUSTOM-TYPES.md | 88 ++++++++++++++++++++++++++++++++++ src/index.ts | 2 +- src/jobs/sendEmailTask.ts | 5 +- src/sendEmail.ts | 29 +++++++++-- src/services/MailingService.ts | 12 ++--- src/types/index.ts | 39 +++++++++++++-- 6 files changed, 159 insertions(+), 16 deletions(-) create mode 100644 CUSTOM-TYPES.md diff --git a/CUSTOM-TYPES.md b/CUSTOM-TYPES.md new file mode 100644 index 0000000..7595440 --- /dev/null +++ b/CUSTOM-TYPES.md @@ -0,0 +1,88 @@ +# 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(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(payload, { + data: { + to: 'user@example.com', + subject: 'Hello', + html: '

Hello World

', + customField: 'my value', // Type-safe! + } +}) +``` + +## ID Type Compatibility + +The plugin works with both: +- **String IDs**: `id: string` +- **Number IDs**: `id: number` + +Your Payload configuration determines which type is used. The plugin automatically adapts to your setup. + +## Type Definitions + +The base interfaces provided by the plugin: + +```typescript +interface BaseEmailDocument { + id: string | number + template?: any + to: string[] + cc?: string[] + bcc?: string[] + from?: string + replyTo?: string + subject: string + html: string + text?: string + variables?: Record + scheduledAt?: string + sentAt?: string + status?: 'pending' | 'processing' | 'sent' | 'failed' + attempts?: number + lastAttemptAt?: string + error?: string + priority?: number + createdAt?: string + updatedAt?: string +} + +interface BaseEmailTemplateDocument { + id: string | number + name: string + slug: string + subject?: string + content?: any + createdAt?: string + updatedAt?: string +} +``` + +These provide a foundation that works with any ID type while maintaining type safety for the core email functionality. \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index bb6cd5b..a5c0995 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,7 +16,7 @@ export { mailingJobs, sendEmailJob } from './jobs/index.js' export type { SendEmailTaskInput } from './jobs/sendEmailTask.js' // Main email sending function -export { sendEmail, type SendEmailOptions } from './sendEmail.js' +export { sendEmail, type SendEmailOptions, type BaseEmailDocument } from './sendEmail.js' export { default as sendEmailDefault } from './sendEmail.js' // Utility functions for developers diff --git a/src/jobs/sendEmailTask.ts b/src/jobs/sendEmailTask.ts index ac8e067..9c65f6f 100644 --- a/src/jobs/sendEmailTask.ts +++ b/src/jobs/sendEmailTask.ts @@ -1,5 +1,4 @@ -import { sendEmail } from '../sendEmail.js' -import {Email, EmailTemplate} from '../payload-types.js' +import { sendEmail, BaseEmailDocument } from '../sendEmail.js' export interface SendEmailTaskInput { // Template mode fields @@ -170,7 +169,7 @@ export const sendEmailJob = { const sendEmailOptions = transformTaskInputToSendEmailOptions(taskInput) // Use the sendEmail helper to create the email - const email = await sendEmail(payload, sendEmailOptions) + const email = await sendEmail(payload, sendEmailOptions) return { output: { diff --git a/src/sendEmail.ts b/src/sendEmail.ts index 25f4616..e38f01e 100644 --- a/src/sendEmail.ts +++ b/src/sendEmail.ts @@ -1,9 +1,32 @@ import { Payload } from 'payload' import { getMailing, renderTemplate, parseAndValidateEmails } from './utils/helpers.js' -import {Email, EmailTemplate} from "./payload-types.js" + +// Generic email interface that can work with any ID type +export interface BaseEmailDocument { + id: string | number + template?: any + to: string[] + cc?: string[] + bcc?: string[] + from?: string + replyTo?: string + subject: string + html: string + text?: string + variables?: Record + scheduledAt?: string + sentAt?: string + status?: 'pending' | 'processing' | 'sent' | 'failed' + attempts?: number + lastAttemptAt?: string + error?: string + priority?: number + createdAt?: string + updatedAt?: string +} // Options for sending emails -export interface SendEmailOptions { +export interface SendEmailOptions { // Template-based email template?: { slug: string @@ -35,7 +58,7 @@ export interface SendEmailOptions { * }) * ``` */ -export const sendEmail = async ( +export const sendEmail = async ( payload: Payload, options: SendEmailOptions ): Promise => { diff --git a/src/services/MailingService.ts b/src/services/MailingService.ts index e7f4f0e..fb28379 100644 --- a/src/services/MailingService.ts +++ b/src/services/MailingService.ts @@ -6,7 +6,7 @@ import { TemplateVariables, MailingService as IMailingService, MailingTransportConfig, - BaseEmail, BaseEmailTemplate + BaseEmail, BaseEmailTemplate, BaseEmailDocument, BaseEmailTemplateDocument } from '../types/index.js' import { serializeRichTextToHTML, serializeRichTextToText } from '../utils/richTextSerializer.js' @@ -131,7 +131,7 @@ export class MailingService implements IMailingService { } const emailContent = await this.renderEmailTemplate(template, variables) - const subject = await this.renderTemplateString(template.subject, variables) + const subject = await this.renderTemplateString(template.subject || '', variables) return { html: emailContent.html, @@ -236,7 +236,7 @@ export class MailingService implements IMailingService { const email = await this.payload.findByID({ collection: this.emailsCollection as any, id: emailId, - }) as BaseEmail + }) as BaseEmailDocument const mailOptions = { from: email.from, @@ -300,7 +300,7 @@ export class MailingService implements IMailingService { return newAttempts } - private async getTemplateBySlug(templateSlug: string): Promise { + private async getTemplateBySlug(templateSlug: string): Promise { try { const { docs } = await this.payload.find({ collection: this.templatesCollection as any, @@ -312,7 +312,7 @@ export class MailingService implements IMailingService { limit: 1, }) - return docs.length > 0 ? docs[0] as BaseEmailTemplate : null + return docs.length > 0 ? docs[0] as BaseEmailTemplateDocument : null } catch (error) { console.error(`Template with slug '${templateSlug}' not found:`, error) return null @@ -377,7 +377,7 @@ export class MailingService implements IMailingService { }) } - private async renderEmailTemplate(template: BaseEmailTemplate, variables: Record = {}): Promise<{ html: string; text: string }> { + private async renderEmailTemplate(template: BaseEmailTemplateDocument, variables: Record = {}): Promise<{ html: string; text: string }> { if (!template.content) { return { html: '', text: '' } } diff --git a/src/types/index.ts b/src/types/index.ts index d14d8d7..ce8736e 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,11 +1,44 @@ import { Payload } from 'payload' import type { CollectionConfig, RichTextField } from 'payload' import { Transporter } from 'nodemailer' -import {Email, EmailTemplate} from "../payload-types.js" -export type BaseEmail = Omit & {template: Omit | TEmailTemplate['id'] | undefined | null} +// Generic base interfaces that work with any ID type +export interface BaseEmailDocument { + id: string | number + template?: any + to: string[] + cc?: string[] + bcc?: string[] + from?: string + replyTo?: string + subject: string + html: string + text?: string + variables?: Record + scheduledAt?: string + sentAt?: string + status?: 'pending' | 'processing' | 'sent' | 'failed' + attempts?: number + lastAttemptAt?: string + error?: string + priority?: number + createdAt?: string + updatedAt?: string +} -export type BaseEmailTemplate = Omit +export interface BaseEmailTemplateDocument { + id: string | number + name: string + slug: string + subject?: string + content?: any + createdAt?: string + updatedAt?: string +} + +export type BaseEmail = Omit & {template: Omit | TEmailTemplate['id'] | undefined | null} + +export type BaseEmailTemplate = Omit export type TemplateRendererHook = (template: string, variables: Record) => string | Promise From 8518c716e88eea22ee2bb5ae7d931e9b785e3be3 Mon Sep 17 00:00:00 2001 From: Bas van den Aakster Date: Sat, 13 Sep 2025 23:21:33 +0200 Subject: [PATCH 2/3] Bump package version to 0.1.13 in `package.json`. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5bf82df..af14f2c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@xtr-dev/payload-mailing", - "version": "0.1.12", + "version": "0.1.13", "description": "Template-based email system with scheduling and job processing for PayloadCMS", "type": "module", "main": "dist/index.js", From 4c495a72b0637580caeb91bc1177117e48bfd4ca Mon Sep 17 00:00:00 2001 From: Bas van den Aakster Date: Sat, 13 Sep 2025 23:22:23 +0200 Subject: [PATCH 3/3] Remove duplicate BaseEmailDocument definition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove duplicate BaseEmailDocument interface from sendEmail.ts - Import BaseEmailDocument from types/index.ts instead - Update sendEmailTask.ts to import from types/index.ts - Maintain single source of truth for BaseEmailDocument type definition 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/index.ts | 2 +- src/jobs/sendEmailTask.ts | 3 ++- src/sendEmail.ts | 25 +------------------------ 3 files changed, 4 insertions(+), 26 deletions(-) diff --git a/src/index.ts b/src/index.ts index a5c0995..bb6cd5b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,7 +16,7 @@ export { mailingJobs, sendEmailJob } from './jobs/index.js' export type { SendEmailTaskInput } from './jobs/sendEmailTask.js' // Main email sending function -export { sendEmail, type SendEmailOptions, type BaseEmailDocument } from './sendEmail.js' +export { sendEmail, type SendEmailOptions } from './sendEmail.js' export { default as sendEmailDefault } from './sendEmail.js' // Utility functions for developers diff --git a/src/jobs/sendEmailTask.ts b/src/jobs/sendEmailTask.ts index 9c65f6f..77ed254 100644 --- a/src/jobs/sendEmailTask.ts +++ b/src/jobs/sendEmailTask.ts @@ -1,4 +1,5 @@ -import { sendEmail, BaseEmailDocument } from '../sendEmail.js' +import { sendEmail } from '../sendEmail.js' +import { BaseEmailDocument } from '../types/index.js' export interface SendEmailTaskInput { // Template mode fields diff --git a/src/sendEmail.ts b/src/sendEmail.ts index e38f01e..63452b4 100644 --- a/src/sendEmail.ts +++ b/src/sendEmail.ts @@ -1,29 +1,6 @@ import { Payload } from 'payload' import { getMailing, renderTemplate, parseAndValidateEmails } from './utils/helpers.js' - -// Generic email interface that can work with any ID type -export interface BaseEmailDocument { - id: string | number - template?: any - to: string[] - cc?: string[] - bcc?: string[] - from?: string - replyTo?: string - subject: string - html: string - text?: string - variables?: Record - scheduledAt?: string - sentAt?: string - status?: 'pending' | 'processing' | 'sent' | 'failed' - attempts?: number - lastAttemptAt?: string - error?: string - priority?: number - createdAt?: string - updatedAt?: string -} +import { BaseEmailDocument } from './types/index.js' // Options for sending emails export interface SendEmailOptions {