From 804a63647aa9bc793832a00a74542dacfcd444ae Mon Sep 17 00:00:00 2001 From: Bas van den Aakster Date: Sat, 13 Sep 2025 18:33:21 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9A=20DOCS:=20Update=20README=20for=20?= =?UTF-8?q?v0.1.0=20API=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove all outdated API examples (sendEmail, scheduleEmail) - Add comprehensive examples using new payload.create() approach - Include template engine configuration options (LiquidJS, Mustache, custom) - Add detailed migration guide from v0.0.x to v0.1.0 - Update feature list to highlight type safety and Payload integration - Replace old API methods section with helper functions - Add template syntax reference for all supported engines - Update Recent Changes section with v0.1.0 breaking changes The README now accurately reflects the simplified collection-based API and provides clear migration paths for existing users. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- README.md | 333 +++++++++++++++++++++++++++++++++------------------ package.json | 2 +- 2 files changed, 218 insertions(+), 117 deletions(-) diff --git a/README.md b/README.md index abe6e84..4b917c8 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,17 @@ 📧 **Template-based email system with scheduling and job processing for PayloadCMS** -⚠️ **Pre-release Warning**: This package is currently in active development (v0.0.x). Breaking changes may occur before v1.0.0. Not recommended for production use. +✨ **Simplified API**: Starting from v0.1.0, this plugin uses a simplified API that leverages PayloadCMS collections directly for better type safety and flexibility. ## Features -✅ **Template System**: Create reusable email templates with Handlebars syntax -✅ **Outbox Scheduling**: Schedule emails for future delivery -✅ **Job Integration**: Automatic processing via PayloadCMS jobs queue -✅ **Retry Failed Sends**: Automatic retry mechanism for failed emails -✅ **Template Variables**: Dynamic content with validation -✅ **Developer API**: Simple methods for sending emails programmatically +✅ **Template System**: Create reusable email templates with LiquidJS, Mustache, or simple variables +✅ **Type Safety**: Full TypeScript support using your generated Payload types +✅ **Flexible Template Engines**: LiquidJS, Mustache, or bring your own template renderer +✅ **Email Scheduling**: Schedule emails for future delivery using Payload collections +✅ **Job Integration**: Automatic processing via PayloadCMS jobs queue +✅ **Retry Failed Sends**: Automatic retry mechanism for failed emails +✅ **Payload Native**: Uses Payload collections directly - no custom APIs to learn ## Installation @@ -49,53 +50,124 @@ export default buildConfig({ }) ``` -### 2. Send emails in your code +### 2. Send emails using Payload collections ```typescript -import { sendEmail, scheduleEmail } from '@xtr-dev/payload-mailing' +import { renderTemplate } from '@xtr-dev/payload-mailing' -// Send immediately using template slug -const emailId = await sendEmail(payload, { - templateSlug: 'welcome-email', - to: 'user@example.com', - variables: { - firstName: 'John', - welcomeUrl: 'https://yoursite.com/welcome' +// Option 1: Using templates with variables +const { html, text, subject } = await renderTemplate(payload, 'welcome-email', { + firstName: 'John', + welcomeUrl: 'https://yoursite.com/welcome' +}) + +// Create email using Payload's collection API (full type safety!) +const email = await payload.create({ + collection: 'emails', + data: { + to: ['user@example.com'], + subject, + html, + text, + // Schedule for later (optional) + scheduledAt: new Date(Date.now() + 24 * 60 * 60 * 1000), + // Add any custom fields you've defined + priority: 1, + customField: 'your-value', // Your custom collection fields work! } }) -// Schedule for later -const scheduledId = await scheduleEmail(payload, { - templateSlug: 'reminder-email', - to: 'user@example.com', - scheduledAt: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24 hours - variables: { - eventName: 'Product Launch', - eventDate: new Date('2024-01-15') +// Option 2: Direct HTML email (no template needed) +const directEmail = await payload.create({ + collection: 'emails', + data: { + to: ['user@example.com'], + subject: 'Welcome!', + html: '

Welcome John!

Thanks for joining!

', + text: 'Welcome John! Thanks for joining!', } }) ``` +## Why This Approach is Better + +- ✅ **Full Type Safety**: Use your generated Payload types +- ✅ **No Learning Curve**: Just use `payload.create()` like any collection +- ✅ **Maximum Flexibility**: Add any custom fields to your email collection +- ✅ **Payload Integration**: Leverage validation, hooks, access control +- ✅ **Consistent API**: One way to create data in Payload + ## Configuration ### Plugin Options ```typescript -interface MailingPluginConfig { - collections?: { - templates?: string // default: 'email-templates' - emails?: string // default: 'emails' +mailingPlugin({ + // Template engine (optional) + templateEngine: 'liquidjs', // 'liquidjs' | 'mustache' | 'simple' + + // Custom template renderer (optional) + templateRenderer: async (template: string, variables: Record) => { + return yourCustomEngine.render(template, variables) + }, + + // Email transport + transport: { + host: 'smtp.gmail.com', + port: 587, + auth: { user: '...', pass: '...' } + }, + + // Collection names (optional) + collections: { + templates: 'email-templates', // default + emails: 'emails' // default + }, + + // Sending options + defaultFrom: 'noreply@yoursite.com', + defaultFromName: 'Your Site', + retryAttempts: 3, // default + retryDelay: 300000, // 5 minutes (default) + + // Advanced options + emailWrapper: (email) => ({ // optional layout wrapper + ...email, + html: `${email.html}` + }), + richTextEditor: lexicalEditor(), // optional custom editor + onReady: async (payload) => { // optional initialization hook + console.log('Mailing plugin ready!') } - defaultFrom?: string - transport?: Transporter | MailingTransportConfig - queue?: string // default: 'default' - retryAttempts?: number // default: 3 - retryDelay?: number // default: 300000 (5 minutes) - emailWrapper?: EmailWrapperHook // optional email layout wrapper - richTextEditor?: RichTextField['editor'] // optional custom rich text editor - onReady?: (payload: any) => Promise // optional callback after plugin initialization - initOrder?: 'before' | 'after' // default: 'before' -} +}) +``` + +### Template Engine Options + +Choose your preferred template engine: + +```typescript +// LiquidJS (default) - Modern syntax with logic +mailingPlugin({ + templateEngine: 'liquidjs' // {% if user.isPremium %}Premium!{% endif %} +}) + +// Mustache - Logic-less templates +mailingPlugin({ + templateEngine: 'mustache' // {{#user.isPremium}}Premium!{{/user.isPremium}} +}) + +// Simple variable replacement +mailingPlugin({ + templateEngine: 'simple' // Just {{variable}} replacement +}) + +// Custom template renderer +mailingPlugin({ + templateRenderer: async (template, variables) => { + return handlebars.compile(template)(variables) // Bring your own! + } +}) ``` ### Transport Configuration @@ -473,71 +545,50 @@ mailingPlugin({ }) ``` -## Handlebars Helpers +## Template Syntax Reference -The plugin includes several built-in helpers: +Depending on your chosen template engine, you can use different syntax: -- `{{formatDate date 'short'}}` - Format dates (short, long, or default) -- `{{formatCurrency amount 'USD'}}` - Format currency -- `{{capitalize string}}` - Capitalize first letter -- `{{#ifEquals value1 value2}}...{{/ifEquals}}` - Conditional equality +### LiquidJS (Default) +- Variables: `{{ user.name }}` +- Logic: `{% if user.isPremium %}Premium content{% endif %}` +- Loops: `{% for item in items %}{{ item.name }}{% endfor %}` +- Filters: `{{ amount | formatCurrency }}`, `{{ date | formatDate: "short" }}` -## API Methods +### Mustache +- Variables: `{{ user.name }}` +- Logic: `{{#user.isPremium}}Premium content{{/user.isPremium}}` +- Loops: `{{#items}}{{ name }}{{/items}}` +- No built-in filters (use variables with pre-formatted data) -### sendEmail(payload, options) +### Simple +- Variables only: `{{ user.name }}`, `{{ amount }}`, `{{ date }}` -Send an email immediately: +### Built-in Filters (LiquidJS only) +- `formatDate` - Format dates: `{{ createdAt | formatDate: "short" }}` +- `formatCurrency` - Format currency: `{{ amount | formatCurrency: "USD" }}` +- `capitalize` - Capitalize first letter: `{{ name | capitalize }}` + +## Available Helper Functions ```typescript -const emailId = await sendEmail(payload, { - templateSlug: 'order-confirmation', // optional - use template slug - to: ['customer@example.com'], // string or array of emails - cc: ['manager@example.com'], // optional - array of emails - bcc: ['archive@example.com'], // optional - array of emails - from: 'orders@yoursite.com', // optional, uses default - replyTo: 'support@yoursite.com', // optional - subject: 'Custom subject', // required if no template - html: '

Custom HTML

', // required if no template - text: 'Custom text version', // optional - variables: { // template variables - orderNumber: '12345', - customerName: 'John Doe' - }, - priority: 1 // optional, 1-10 (1 = highest) +import { + renderTemplate, // Render email templates with variables + processEmails, // Process pending emails manually + retryFailedEmails, // Retry failed emails + getMailing // Get mailing service instance +} from '@xtr-dev/payload-mailing' + +// Render a template +const { html, text, subject } = await renderTemplate(payload, 'welcome', { + name: 'John', + activationUrl: 'https://example.com/activate' }) -``` -### scheduleEmail(payload, options) - -Schedule an email for later delivery: - -```typescript -const emailId = await scheduleEmail(payload, { - templateSlug: 'newsletter', - to: ['user1@example.com', 'user2@example.com'], - scheduledAt: new Date('2024-01-15T10:00:00Z'), - variables: { - month: 'January', - highlights: ['Feature A', 'Feature B'] - } -}) -``` - -### processEmails(payload) - -Manually process pending emails: - -```typescript -import { processEmails } from '@xtr-dev/payload-mailing' +// Process emails manually await processEmails(payload) -``` -### retryFailedEmails(payload) - -Manually retry failed emails: - -```typescript -import { retryFailedEmails } from '@xtr-dev/payload-mailing' +// Retry failed emails await retryFailedEmails(payload) ``` @@ -716,35 +767,85 @@ import { } from '@xtr-dev/payload-mailing' ``` +## Migration Guide (v0.0.x → v0.1.0) + +**🚨 BREAKING CHANGES**: The API has been simplified to use Payload collections directly. + +### Before (v0.0.x) +```typescript +import { sendEmail, scheduleEmail } from '@xtr-dev/payload-mailing' + +// Old way +const emailId = await sendEmail(payload, { + templateSlug: 'welcome', + to: 'user@example.com', + variables: { name: 'John' } +}) + +const scheduledId = await scheduleEmail(payload, { + templateSlug: 'reminder', + to: 'user@example.com', + scheduledAt: new Date('2024-01-15T10:00:00Z'), + variables: { eventName: 'Launch' } +}) +``` + +### After (v0.1.0+) +```typescript +import { renderTemplate } from '@xtr-dev/payload-mailing' + +// New way - render template first +const { html, text, subject } = await renderTemplate(payload, 'welcome', { + name: 'John' +}) + +// Then create email using Payload collections (full type safety!) +const email = await payload.create({ + collection: 'emails', + data: { + to: ['user@example.com'], + subject, + html, + text, + // For scheduling + scheduledAt: new Date('2024-01-15T10:00:00Z'), + // Add any custom fields from your collection + customField: 'value', + } +}) +``` + +### Benefits of Migration +- ✅ **Full TypeScript support** with your generated Payload types +- ✅ **Use any custom fields** you add to your email collection +- ✅ **Leverage Payload's features**: validation, hooks, access control +- ✅ **One consistent API** - just use `payload.create()` +- ✅ **No wrapper methods** - direct access to Payload's power + ## Recent Changes -### v0.0.x (Latest) +### v0.1.0 (Latest - Breaking Changes) -**🔄 Breaking Changes:** -- Removed email layouts system in favor of `emailWrapper` hook for better flexibility -- Email fields (`to`, `cc`, `bcc`) now use `hasMany: true` for proper array handling -- Templates now use slug-based lookup instead of ID-based for developer-friendly API -- Email collection renamed from "outbox" to "emails" -- Unified job processing: single `process-email-queue` job handles both pending and failed emails +**🚀 Major API Simplification:** +- **REMOVED**: `sendEmail()` and `scheduleEmail()` wrapper methods +- **REMOVED**: `SendEmailOptions` custom types +- **ADDED**: Direct Payload collection usage with full type safety +- **ADDED**: `renderTemplate()` helper for template rendering +- **ADDED**: Support for LiquidJS, Mustache, and custom template engines +- **IMPROVED**: Webpack compatibility with proper dynamic imports -**✨ New Features:** -- Rich text editor with automatic HTML/text conversion -- Template slugs for easier template reference -- `emailWrapper` hook for consistent email layouts -- Custom rich text editor configuration support -- Initialization hooks (`onReady`, `initOrder`) for better plugin lifecycle control -- Improved Handlebars variable interpolation with defensive programming +**Template Engine Enhancements:** +- **NEW**: LiquidJS support (default) with modern syntax and logic +- **NEW**: Mustache support for logic-less templates +- **NEW**: Custom template renderer hook for maximum flexibility +- **NEW**: Simple variable replacement as fallback +- **FIXED**: All webpack compatibility issues resolved -**🐛 Bug Fixes:** -- Fixed text version uppercase conversion in headings -- Fixed Handlebars interpolation issues in text version -- Improved plugin initialization order to prevent timing issues - -**💡 Improvements:** -- Better admin UI with proper array input controls -- More robust error handling and logging -- Enhanced TypeScript definitions -- Simplified template creation workflow +**Developer Experience:** +- **IMPROVED**: Full TypeScript inference using generated Payload types +- **IMPROVED**: Comprehensive migration guide and documentation +- **IMPROVED**: Better error handling and async patterns +- **SIMPLIFIED**: Cleaner codebase with fewer abstractions ## License diff --git a/package.json b/package.json index 71c4844..27d97aa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@xtr-dev/payload-mailing", - "version": "0.1.0", + "version": "0.1.1", "description": "Template-based email system with scheduling and job processing for PayloadCMS", "type": "module", "main": "dist/index.js",