Compare commits

..

13 Commits

Author SHA1 Message Date
Bas
7f04275d39 Merge pull request #33 from xtr-dev/dev
Dev
2025-09-13 23:53:56 +02:00
20afe30e88 Fix scheduledAt type in SendEmailTaskInput and add Date normalization
- Update SendEmailTaskInput.scheduledAt to support string | Date types
- Add Date object normalization to ISO strings in sendEmail processing
- Ensure consistent database storage format for all timestamp fields
- Convert Date objects to ISO strings before database operations

Resolves remaining "Type Date is not assignable to type string" error
for scheduledAt field in job task input.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-13 23:53:25 +02:00
02b3fecadf Bump package version to 0.1.17 in package.json. 2025-09-13 23:52:53 +02:00
Bas
ea87f14308 Merge pull request #32 from xtr-dev/dev
Dev
2025-09-13 23:48:28 +02:00
6886027727 Bump package version to 0.1.16 in package.json. 2025-09-13 23:45:39 +02:00
965569be06 Add Date type support for timestamp fields
- Update scheduledAt, sentAt, lastAttemptAt, createdAt, updatedAt fields to support Date | string | null
- Support both Date objects and ISO string formats for all timestamp fields
- Update BaseEmailDocument, BaseEmailTemplateDocument, and QueuedEmail interfaces consistently
- Update documentation to reflect Date object compatibility

Fixes type constraint error where customer timestamp fields use Date objects
but plugin interfaces only supported string formats.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-13 23:44:57 +02:00
Bas
ff788c1ecf Merge pull request #31 from xtr-dev/dev
Fix variables field type to support all JSON-compatible values
2025-09-13 23:41:43 +02:00
c12438aaa2 Bump package version to 0.1.15 in package.json. 2025-09-13 23:40:31 +02:00
4dcbc1446a Fix variables field type to support all JSON-compatible values
- Replace restrictive Record<string, any> with flexible JSONValue type for variables field
- Add JSONValue type alias that matches Payload's JSON field type specification
- Support string, number, boolean, objects, arrays, null, and undefined for variables
- Update both BaseEmailDocument and QueuedEmail interfaces consistently
- Update documentation to reflect JSONValue support

Fixes type constraint error where customer Email.variables field type
(string | number | boolean | {...} | unknown[] | null | undefined)
was not assignable to Record<string, any>.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-13 23:38:46 +02:00
Bas
72f3d7f66d Merge pull request #30 from xtr-dev/dev
Add null value support to BaseEmailDocument interface
2025-09-13 23:35:25 +02:00
ecc0b0a73e Fix type inconsistencies and missing null checks
- Update QueuedEmail interface to include `| null` for optional fields to match BaseEmailDocument
- Add missing null checks for replyTo and from fields in sendEmail processing
- Add proper email validation for replyTo and from fields (single email addresses)
- Ensure type consistency across all email-related interfaces

Fixes potential type conflicts between QueuedEmail and BaseEmailDocument,
and ensures all nullable email fields are properly validated.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-13 23:32:44 +02:00
a959673fc1 Bump package version to 0.1.14 in package.json. 2025-09-13 23:31:23 +02:00
8809db6aff Add null value support to BaseEmailDocument interface
- Update BaseEmailDocument to support `| null` for optional fields (cc, bcc, from, replyTo, text, etc.)
- Update BaseEmailTemplateDocument to support `| null` for optional fields
- Add explicit null checks in sendEmail processing to handle null values properly
- Update CUSTOM-TYPES.md documentation to reflect null value compatibility

Fixes type constraint error where customer Email types had `cc?: string[] | null`
but BaseEmailDocument only supported `cc?: string[]`.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-13 23:27:53 +02:00
5 changed files with 93 additions and 56 deletions

View File

@@ -38,50 +38,57 @@ const customEmail = await sendEmail<MyEmail>(payload, {
}) })
``` ```
## ID Type Compatibility ## Compatibility
The plugin works with both: The plugin works with:
- **String IDs**: `id: string` - **String IDs**: `id: string`
- **Number IDs**: `id: number` - **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 type is used. The plugin automatically adapts to your setup. Your Payload configuration determines which types are used. The plugin automatically adapts to your setup.
## Type Definitions ## Type Definitions
The base interfaces provided by the plugin: The base interfaces provided by the plugin:
```typescript ```typescript
// JSON value type that matches Payload's JSON field type
type JSONValue = string | number | boolean | { [k: string]: unknown } | unknown[] | null | undefined
interface BaseEmailDocument { interface BaseEmailDocument {
id: string | number id: string | number
template?: any template?: any
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 // Supports any JSON-compatible value
scheduledAt?: string scheduledAt?: string | Date | null
sentAt?: string sentAt?: string | Date | null
status?: 'pending' | 'processing' | 'sent' | 'failed' status?: 'pending' | 'processing' | 'sent' | 'failed' | null
attempts?: number attempts?: number | null
lastAttemptAt?: string lastAttemptAt?: string | Date | null
error?: string error?: string | null
priority?: number priority?: number | null
createdAt?: string createdAt?: string | Date | null
updatedAt?: string updatedAt?: string | Date | null
} }
interface BaseEmailTemplateDocument { interface BaseEmailTemplateDocument {
id: string | number id: string | number
name: string name: string
slug: string slug: string
subject?: string subject?: string | null
content?: any content?: any
createdAt?: string createdAt?: string | Date | null
updatedAt?: string updatedAt?: string | Date | null
} }
``` ```

View File

@@ -1,6 +1,6 @@
{ {
"name": "@xtr-dev/payload-mailing", "name": "@xtr-dev/payload-mailing",
"version": "0.1.13", "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",

View File

@@ -15,7 +15,7 @@ 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

View File

@@ -83,12 +83,39 @@ export const sendEmail = async <TEmail extends BaseEmailDocument = BaseEmailDocu
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({

View File

@@ -2,38 +2,41 @@ import { Payload } from 'payload'
import type { CollectionConfig, RichTextField } from 'payload' import type { CollectionConfig, RichTextField } from 'payload'
import { Transporter } from 'nodemailer' import { Transporter } from 'nodemailer'
// Generic base interfaces that work with any ID type // 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 { export interface BaseEmailDocument {
id: string | number id: string | number
template?: any template?: any
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' | null
attempts?: number attempts?: number | null
lastAttemptAt?: string lastAttemptAt?: string | Date | null
error?: string error?: string | null
priority?: number priority?: number | null
createdAt?: string createdAt?: string | Date | null
updatedAt?: string updatedAt?: string | Date | null
} }
export interface BaseEmailTemplateDocument { export interface BaseEmailTemplateDocument {
id: string | number id: string | number
name: string name: string
slug: string slug: string
subject?: string subject?: string | null
content?: any content?: any
createdAt?: string createdAt?: string | Date | null
updatedAt?: string 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 BaseEmail<TEmail extends BaseEmailDocument = BaseEmailDocument, TEmailTemplate extends BaseEmailTemplateDocument = BaseEmailTemplateDocument> = Omit<TEmail, 'id' | 'template'> & {template: Omit<TEmailTemplate, 'id'> | TEmailTemplate['id'] | undefined | null}
@@ -75,23 +78,23 @@ export interface MailingTransportConfig {
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
} }