Compare commits

...

4 Commits

Author SHA1 Message Date
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
4 changed files with 67 additions and 55 deletions

View File

@@ -38,13 +38,15 @@ const customEmail = await sendEmail<MyEmail>(payload, {
})
```
## ID Type Compatibility
## Compatibility
The plugin works with both:
The plugin works with:
- **String IDs**: `id: string`
- **Number IDs**: `id: number`
- **Nullable fields**: Fields can be `null`, `undefined`, or have values
- **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
@@ -55,33 +57,33 @@ interface BaseEmailDocument {
id: string | number
template?: any
to: string[]
cc?: string[]
bcc?: string[]
from?: string
replyTo?: string
cc?: string[] | null
bcc?: string[] | null
from?: string | null
replyTo?: string | null
subject: string
html: string
text?: string
variables?: Record<string, any>
scheduledAt?: string
sentAt?: string
status?: 'pending' | 'processing' | 'sent' | 'failed'
attempts?: number
lastAttemptAt?: string
error?: string
priority?: number
createdAt?: string
updatedAt?: string
text?: string | null
variables?: Record<string, any> | null
scheduledAt?: string | null
sentAt?: string | null
status?: 'pending' | 'processing' | 'sent' | 'failed' | null
attempts?: number | null
lastAttemptAt?: string | null
error?: string | null
priority?: number | null
createdAt?: string | null
updatedAt?: string | null
}
interface BaseEmailTemplateDocument {
id: string | number
name: string
slug: string
subject?: string
subject?: string | null
content?: any
createdAt?: string
updatedAt?: string
createdAt?: string | null
updatedAt?: string | null
}
```

View File

@@ -1,6 +1,6 @@
{
"name": "@xtr-dev/payload-mailing",
"version": "0.1.13",
"version": "0.1.14",
"description": "Template-based email system with scheduling and job processing for PayloadCMS",
"type": "module",
"main": "dist/index.js",

View File

@@ -83,12 +83,22 @@ export const sendEmail = async <TEmail extends BaseEmailDocument = BaseEmailDocu
if (emailData.to) {
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[])
}
if (emailData.bcc) {
if (emailData.bcc && emailData.bcc !== null) {
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
}
// Create the email in the collection with proper typing
const email = await payload.create({

View File

@@ -2,38 +2,38 @@ import { Payload } from 'payload'
import type { CollectionConfig, RichTextField } from 'payload'
import { Transporter } from 'nodemailer'
// Generic base interfaces that work with any ID type
// Generic base interfaces that work with any ID type and null values
export interface BaseEmailDocument {
id: string | number
template?: any
to: string[]
cc?: string[]
bcc?: string[]
from?: string
replyTo?: string
cc?: string[] | null
bcc?: string[] | null
from?: string | null
replyTo?: string | null
subject: string
html: string
text?: string
variables?: Record<string, any>
scheduledAt?: string
sentAt?: string
status?: 'pending' | 'processing' | 'sent' | 'failed'
attempts?: number
lastAttemptAt?: string
error?: string
priority?: number
createdAt?: string
updatedAt?: string
text?: string | null
variables?: Record<string, any> | null
scheduledAt?: string | null
sentAt?: string | null
status?: 'pending' | 'processing' | 'sent' | 'failed' | null
attempts?: number | null
lastAttemptAt?: string | null
error?: string | null
priority?: number | null
createdAt?: string | null
updatedAt?: string | null
}
export interface BaseEmailTemplateDocument {
id: string | number
name: string
slug: string
subject?: string
subject?: string | null
content?: any
createdAt?: string
updatedAt?: string
createdAt?: string | null
updatedAt?: string | 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 +75,23 @@ export interface MailingTransportConfig {
export interface QueuedEmail {
id: string
template?: string
template?: string | null
to: string[]
cc?: string[]
bcc?: string[]
from?: string
replyTo?: string
cc?: string[] | null
bcc?: string[] | null
from?: string | null
replyTo?: string | null
subject: string
html: string
text?: string
variables?: Record<string, any>
scheduledAt?: string
sentAt?: string
text?: string | null
variables?: Record<string, any> | null
scheduledAt?: string | null
sentAt?: string | null
status: 'pending' | 'processing' | 'sent' | 'failed'
attempts: number
lastAttemptAt?: string
error?: string
priority?: number
lastAttemptAt?: string | null
error?: string | null
priority?: number | null
createdAt: string
updatedAt: string
}