# @xtr-dev/payload-mailing šŸ“§ **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. ## 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 ## Installation ```bash npm install @xtr-dev/payload-mailing ``` ## Quick Start ### 1. Add the plugin to your Payload config ```typescript import { buildConfig } from 'payload/config' import { mailingPlugin } from '@xtr-dev/payload-mailing' export default buildConfig({ // ... your config plugins: [ mailingPlugin({ defaultFrom: 'noreply@yoursite.com', transport: { host: 'smtp.gmail.com', port: 587, secure: false, auth: { user: process.env.EMAIL_USER, pass: process.env.EMAIL_PASS, }, }, retryAttempts: 3, retryDelay: 300000, // 5 minutes queue: 'email-queue', // optional }), ], }) ``` ### 2. Send emails in your code ```typescript import { sendEmail, scheduleEmail } 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' } }) // 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') } }) ``` ## Configuration ### Plugin Options ```typescript interface MailingPluginConfig { collections?: { templates?: string // default: 'email-templates' emails?: string // default: 'emails' } 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' } ``` ### Transport Configuration You can provide either a Nodemailer transporter instance or configuration: ```typescript // Using configuration object { transport: { host: 'smtp.gmail.com', port: 587, secure: false, auth: { user: process.env.EMAIL_USER, pass: process.env.EMAIL_PASS, }, } } // Or using a transporter instance import nodemailer from 'nodemailer' { transport: nodemailer.createTransporter({ // your config }) } ``` ## Creating Email Templates 1. Go to your Payload admin panel 2. Navigate to **Mailing > Email Templates** 3. Create a new template with: - **Name**: Descriptive name for the template - **Slug**: Unique identifier for the template (auto-generated) - **Subject**: Email subject (supports Handlebars) - **Content**: Rich text editor with Handlebars syntax (automatically generates HTML and text versions) ### Template Example **Subject**: `Welcome to {{siteName}}, {{firstName}}!` **Content** (using rich text editor with Handlebars): ``` # Welcome {{firstName}}! šŸŽ‰ Thanks for joining {{siteName}}. We're excited to have you! **What you can do:** • Create beautiful emails with rich text formatting • Use the emailWrapper hook to add custom layouts • Queue and schedule emails effortlessly Your account was created on {{formatDate createdAt "long"}}. Best regards, The {{siteName}} Team ``` ## Advanced Features ### Email Wrapper Hook Use the `emailWrapper` hook to apply consistent layouts to all emails: ```typescript mailingPlugin({ // ... other config emailWrapper: (email) => { const wrappedHtml = ` ${email.subject}

My Company

${email.html}
` return { ...email, html: wrappedHtml, text: `MY COMPANY\n\n${email.text}\n\n© 2024 My Company` } } }) ``` ### Custom Rich Text Editor Override the rich text editor used for templates: ```typescript import { lexicalEditor } from '@payloadcms/richtext-lexical' import { FixedToolbarFeature, HeadingFeature } from '@payloadcms/richtext-lexical' mailingPlugin({ // ... other config richTextEditor: lexicalEditor({ features: ({ defaultFeatures }) => [ ...defaultFeatures, FixedToolbarFeature(), HeadingFeature({ enabledHeadingSizes: ['h1', 'h2', 'h3'] }), // Add more features as needed ], }) }) ``` ### Initialization Hooks Control plugin initialization order and add post-initialization logic: ```typescript mailingPlugin({ // ... other config initOrder: 'after', // Initialize after main Payload onInit onReady: async (payload) => { // Called after plugin is fully initialized console.log('Mailing plugin ready!') // Custom initialization logic here await setupCustomEmailSettings(payload) } }) ``` ## Handlebars Helpers The plugin includes several built-in helpers: - `{{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 ## API Methods ### sendEmail(payload, options) Send an email immediately: ```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) }) ``` ### 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' await processEmails(payload) ``` ### retryFailedEmails(payload) Manually retry failed emails: ```typescript import { retryFailedEmails } from '@xtr-dev/payload-mailing' await retryFailedEmails(payload) ``` ## Job Processing The plugin automatically adds a unified email processing job to PayloadCMS: - **Job Name**: `process-email-queue` - **Function**: Processes both pending emails and retries failed emails - **Trigger**: Manual via admin panel or API call The job is automatically registered when the plugin initializes. To trigger it manually: ```typescript // Queue the job for processing await payload.jobs.queue({ task: 'process-email-queue', input: {} }) ``` ## Email Status Tracking All emails are stored in the emails collection with these statuses: - `pending` - Waiting to be sent - `processing` - Currently being sent - `sent` - Successfully delivered - `failed` - Failed to send (will retry if attempts < retryAttempts) ## Monitoring Check the **Mailing > Emails** collection in your admin panel to: - View email delivery status - See error messages for failed sends - Track retry attempts - Monitor scheduled emails ## Environment Variables ```bash # Email configuration EMAIL_HOST=smtp.gmail.com EMAIL_PORT=587 EMAIL_USER=your-email@gmail.com EMAIL_PASS=your-app-password EMAIL_FROM=noreply@yoursite.com ``` ## TypeScript Support The plugin includes full TypeScript definitions. Import types as needed: ```typescript import { MailingPluginConfig, SendEmailOptions, EmailTemplate, QueuedEmail, EmailObject, EmailWrapperHook } from '@xtr-dev/payload-mailing' ``` ## Recent Changes ### v0.0.x (Latest) **šŸ”„ 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 **✨ 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 **šŸ› 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 ## License MIT ## Contributing Issues and pull requests welcome at [GitHub repository](https://github.com/xtr-dev/payload-mailing)