# @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 ### Custom HTML Layouts with Email Wrapper Hook The `emailWrapper` hook allows you to apply consistent HTML layouts and styling to all emails sent through the plugin. This is perfect for adding company branding, headers, footers, and responsive styling. #### Basic Email Wrapper ```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\nUnsubscribe: [link] | Contact Support: [link]` } } }) ``` #### Advanced Email Wrapper with Dynamic Content ```typescript mailingPlugin({ // ... other config emailWrapper: (email) => { // You can access email properties and customize based on content const isTransactional = email.subject?.includes('Receipt') || email.subject?.includes('Confirmation'); const headerColor = isTransactional ? '#28a745' : '#007bff'; const headerText = isTransactional ? 'Order Confirmation' : 'My Company'; const wrappedHtml = ` ${email.subject}
` // Also enhance the plain text version const wrappedText = ` ${headerText.toUpperCase()} ${'='.repeat(headerText.length)} ${email.text || email.html?.replace(/<[^>]*>/g, '')} --- Ā© ${new Date().getFullYear()} My Company. All rights reserved. Contact Support: support@mycompany.com Privacy Policy: [link] Unsubscribe: [link] `.trim(); return { ...email, html: wrappedHtml, text: wrappedText } } }) ``` #### External CSS and Assets You can also reference external stylesheets and assets: ```typescript mailingPlugin({ emailWrapper: (email) => { const wrappedHtml = ` ${email.subject}
My Company
${email.html}
Footer
`; return { ...email, html: wrappedHtml }; } }) ``` #### Template-Specific Layouts You can customize layouts based on email templates: ```typescript mailingPlugin({ emailWrapper: (email, context) => { // Access template information if available const templateSlug = context?.templateSlug; let layoutClass = 'default-layout'; let headerColor = '#007bff'; if (templateSlug === 'welcome-email') { layoutClass = 'welcome-layout'; headerColor = '#28a745'; } else if (templateSlug === 'invoice-email') { layoutClass = 'invoice-layout'; headerColor = '#dc3545'; } const wrappedHtml = ` ${email.subject}
${email.html}
`; return { ...email, html: wrappedHtml }; } }) ``` The `emailWrapper` hook receives the email object with `html`, `text`, and `subject` properties, and should return the modified email object with your custom layout applied. ### 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 ``` ## Security and Access Control ### Collection Access Restrictions By default, both email templates and emails collections allow full access (`read/create/update/delete: () => true`). For production use, you should configure proper access restrictions using collection overrides: ```typescript mailingPlugin({ // ... other config collections: { templates: { access: { read: ({ req: { user } }) => { if (!user) return false return user.role === 'admin' || user.permissions?.includes('mailing:read') }, create: ({ req: { user } }) => { if (!user) return false return user.role === 'admin' || user.permissions?.includes('mailing:create') }, update: ({ req: { user } }) => { if (!user) return false return user.role === 'admin' || user.permissions?.includes('mailing:update') }, delete: ({ req: { user } }) => { if (!user) return false return user.role === 'admin' }, } }, emails: { access: { read: ({ req: { user } }) => { if (!user) return false return user.role === 'admin' || user.permissions?.includes('mailing:read') }, create: ({ req: { user } }) => { if (!user) return false return user.role === 'admin' || user.permissions?.includes('mailing:create') }, update: ({ req: { user } }) => { if (!user) return false return user.role === 'admin' || user.permissions?.includes('mailing:update') }, delete: ({ req: { user } }) => { if (!user) return false return user.role === 'admin' }, } } } }) ``` ### Collection Overrides You can override any collection configuration using the `collections.templates` or `collections.emails` options. This includes: - **Access controls** - Restrict who can read/create/update/delete - **Admin UI settings** - Customize admin interface appearance - **Field modifications** - Add custom fields or modify existing ones - **Hooks** - Add custom validation or processing logic Example with additional custom fields: ```typescript mailingPlugin({ // ... other config collections: { templates: { admin: { group: 'Custom Marketing', description: 'Custom email templates with enhanced features' }, fields: [ // Plugin's default fields are preserved { name: 'category', type: 'select', options: [ { label: 'Marketing', value: 'marketing' }, { label: 'Transactional', value: 'transactional' }, { label: 'System', value: 'system' } ], admin: { position: 'sidebar' } }, { name: 'tags', type: 'text', hasMany: true, admin: { description: 'Tags for organizing templates' } } ], hooks: { beforeChange: [ ({ data, req }) => { // Custom validation logic if (data.category === 'system' && req.user?.role !== 'admin') { throw new Error('Only admins can create system templates') } return data } ] } } } }) ``` ## 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)