@flexiweb/email-templates
v3.66.0
Published
Email-Template builder plugin for Flexiweb powered by grapesjs and MJML
Downloads
146
Maintainers
Readme
@flexiweb/email-templates
A comprehensive email template plugin for PayloadCMS v3 providing MJML-based email template editing, validations, and utilities for the Flexiweb ecosystem.
Quick Links: License • Documentation • Website
Table of Contents
- Introduction
- Features
- Getting Started
- Collections
- Validations
- Email Template Editor
- Constants & Defaults
- Templating System
- Custom Fields
- Translations
- Utilities
- Overrides & Customization
- License
Introduction
@flexiweb/email-templates is a powerful, production-ready plugin for PayloadCMS v3 that provides a comprehensive email template management system. Built with GrapesJS and MJML, this plugin enables visual email template editing while maintaining clean, responsive HTML output.
The plugin is designed to work seamlessly with the Flexiweb ecosystem and provides:
- Visual Email Editor: Drag-and-drop email template builder powered by GrapesJS and MJML
- Template Variables: Dynamic content substitution using the Eta templating engine
- Validation System: Ensures MJML integrity and prevents invalid template states
- Media Management: Dedicated collection for email template assets
- Access Control: Role-based permissions for template management
- Full Overridability: Customize every aspect of the plugin to fit your needs
Key Principles
- 🎨 Visual Editing: Intuitive drag-and-drop interface for creating email templates
- 📧 MJML-Based: Uses MJML for clean, responsive email HTML generation
- 🔧 Fully Overridable: Every component can be customized or extended
- 🌍 i18n Ready: Built-in support for multiple languages (German, English)
- 🛡️ Type Safe: Full TypeScript support with comprehensive type definitions
- ♿ Accessible: Components follow accessibility best practices
Features
🎨 Visual Email Editor
- Drag-and-drop interface powered by GrapesJS
- MJML component support for responsive email design
- Real-time preview
- Image upload and management
- Save functionality with automatic HTML generation
📝 Template Management
- Create, read, update, and delete email templates
- Template type system for organizing templates
- Enable/disable templates
- Variable documentation system
✅ Validation System
- MJML syntax validation
- Prevents use of
<mj-raw>tags for security - Ensures both MJML and HTML are synchronized
- HTML editing restrictions (only via editor)
🖼️ Media Management
- Dedicated media collection for email assets
- Image upload support
- Automatic asset loading in editor
🔐 Access Control
- Role-based permissions
- System-level access for critical operations
- Editor-level access for content management
Getting Started
Installation
Install the package using your preferred package manager:
# Using pnpm (recommended)
pnpm add @flexiweb/email-templates
# Using npm
npm install @flexiweb/email-templates
# Using yarn
yarn add @flexiweb/email-templatesPeer Dependencies
This plugin requires the following peer dependencies:
[email protected]@payloadcms/[email protected]@flexiweb/[email protected]
Basic Usage
1. Add the Plugin to Your Payload Config
import { flexiwebEmailTemplatesPlugin } from '@flexiweb/email-templates'
import { buildConfig } from 'payload'
export default buildConfig({
plugins: [
flexiwebEmailTemplatesPlugin({
// Optional: Configure plugin options
overrides: {
emailTemplates: {
// Customize email templates collection
},
},
}),
],
// ... rest of your config
})2. Access the Email Template Editor
Once a template is created, you can access the visual editor by:
- Opening an email template in the admin panel
- Clicking the "Open in editor" button in the "Email content" field
- Or navigate directly to:
/admin/email-templates/{templateId}/editor
3. Use Templates in Your Application
import { substitueVariablesInString } from '@flexiweb/email-templates/utils'
// Fetch your template
const template = await payload.find({
collection: 'email-templates',
where: { type: { equals: 'welcome-email' } },
})
// Prepare variables
const variables = {
recipient: {
firstName: 'John',
lastName: 'Doe',
fullName: 'John Doe',
email: '[email protected]',
},
handler: {
email: '[email protected]',
fullName: 'Support Team',
},
currentDate: new Date().toLocaleDateString(),
currentYear: new Date().getFullYear().toString(),
currentMonth: (new Date().getMonth() + 1).toString(),
currentDay: new Date().getDate().toString(),
}
// Render the template
const renderedHtml = substitueVariablesInString(template.docs[0].html, variables)Collections
Email Templates Collection
The email-templates collection manages all email templates in your system.
Fields
enabled(checkbox): Whether the template is activename(text, required): Template nametype(select, required, unique): Template type identifiermjml(textarea, read-only): MJML source code (edited in visual editor)html(textarea, read-only): Generated HTML (edited in visual editor)variables(array): Documentation of available template variablesvariable(text, required): Variable name (e.g.,<%= it.recipient.firstName %>)description(text): Description of what the variable represents
Access Rules
- Create: System users only
- Read: Editor users and above
- Update: Editor users and above
- Delete: System users only
Note: The variables field can only be updated by system users.
Default Columns
nametype
Admin Configuration
- Group: "Templates" (localized)
- Title Field:
name
Email Template Media Collection
The email-template-media collection stores media assets used in email templates.
Fields
- Standard Payload upload fields
Upload Configuration
- MIME Types:
image/* - Static Directory:
files/flexiweb/email-templates/media
Access Rules
- Create: Editor users and above
- Read: Public (all users)
- Update: Editor users and above
- Delete: Editor users and above
Admin Configuration
- Group: "Templates" (localized)
Validations
MJML Validation
The isValidCleanMjml validation ensures that:
- The MJML is valid and starts with
<mjml> - No
<mj-raw>tags are present (security restriction) - Respects override rules for system users
Usage:
import { isValidCleanMjml } from '@flexiweb/email-templates/validations'
{
name: 'mjml',
type: 'textarea',
validate: isValidCleanMjml(),
}With Override:
{
name: 'mjml',
type: 'textarea',
validate: isValidCleanMjml({ override: false }),
}MJML and HTML Changed Validation
The bothMjmlAndHtmlChanged validation ensures that when updating a template, both MJML and HTML fields are provided together. This maintains synchronization between the source MJML and the generated HTML.
Usage:
import { bothMjmlAndHtmlChanged } from '@flexiweb/email-templates/validations'
{
name: 'mjml',
type: 'textarea',
validate: bothMjmlAndHtmlChanged(),
}Note: This validation only runs on update operations, not on create.
HTML Field Protection
The HTML field has additional validation that:
- Allows system users to edit HTML directly
- Only allows HTML editing from the admin panel (via the visual editor)
- Prevents direct API updates to HTML without MJML
This ensures HTML is always generated from MJML through the visual editor, maintaining consistency.
Email Template Editor
How It Works
The email template editor is a custom PayloadCMS admin view that provides a visual interface for creating and editing email templates.
Architecture
- GrapesJS: Provides the drag-and-drop interface
- grapesjs-mjml: Adds MJML component support
- Real-time Preview: Shows how the email will look
- Save Command: Converts MJML to HTML and saves both
Editor Initialization
When the editor loads:
- Fetches the template's current MJML (or uses default)
- Initializes GrapesJS with MJML plugin
- Disables
<mj-raw>blocks for security - Loads existing media assets
- Sets up save functionality
Save Process
When saving:
- Extracts MJML from the editor
- Extracts rendered HTML from the preview iframe
- Sends both to the Payload API via PATCH request
- Updates both
mjmlandhtmlfields simultaneously
Editor Features
- Drag-and-Drop Components: Add MJML components visually
- Component Library: Access to all MJML components (sections, columns, text, images, buttons, etc.)
- Image Upload: Upload images directly from the editor
- Asset Management: Browse and reuse previously uploaded images
- Real-time Preview: See changes as you edit
- Responsive Design: Preview on different screen sizes
- Save Button: Custom save command in the editor toolbar
Accessing the Editor
The editor is accessible at:
/admin/email-templates/{templateId}/editorA "Redirect to Editor" button field is included in the template form for easy access.
Constants & Defaults
Default Template Variables
The plugin provides a set of default template variables that can be used as examples or starting points:
import { DEFAULT_EMAIL_TEMPLATE_VARIABLES } from '@flexiweb/email-templates/constants'
// Available variables:
// - <%= it.recipient.firstName %>
// - <%= it.recipient.lastName %>
// - <%= it.recipient.fullName %>
// - <%= it.recipient.email %>
// - <%= it.handler.email %>
// - <%= it.handler.fullName %>
// - <%= it.currentDate %>
// - <%= it.currentYear %>
// - <%= it.currentMonth %>
// - <%= it.currentDay %>Important: These are indicative only. The actual variables available depend on what you pass when rendering the template. See Templating System for details.
Default MJML Template
A default MJML template is provided for new templates:
import { DEFAULT_EMAIL_TEMPLATE_MJML } from '@flexiweb/email-templates/constants'This template includes:
- Basic structure with header, body, and footer
- Example sections demonstrating MJML components
- Sample variable usage
- Responsive layout examples
Default HTML Template
A corresponding default HTML template is also available:
import { DEFAULT_EMAIL_TEMPLATE_HTML } from '@flexiweb/email-templates/constants'Media Static Directory
The static directory for email template media:
import { EMAIL_TEMPLATE_MEDIA_STATIC_DIR } from '@flexiweb/email-templates/constants'
// Value: 'files/flexiweb/email-templates/media'Templating System
Variable Syntax
The plugin uses the Eta templating engine for variable substitution. Variables follow this syntax:
<%= it.variableName %>For nested objects:
<%= it.recipient.firstName %>
<%= it.recipient.email %>Using Variables
Variables shown in the template's variables field are indicative only. They document what variables are intended to be used, but the actual variables available depend on what you pass when rendering the template.
Example Template
<mj-text>
Hello <%= it.recipient.firstName %>!
Your order <%= it.orderNumber %> has been processed.
Thank you,
<%= it.handler.fullName %>
</mj-text>Rendering the Template
import { substitueVariablesInString } from '@flexiweb/email-templates/utils'
const template = await payload.find({
collection: 'email-templates',
where: { type: { equals: 'order-confirmation' } },
})
const variables = {
recipient: {
firstName: 'John',
},
orderNumber: '12345',
handler: {
fullName: 'Support Team',
},
}
const renderedHtml = substitueVariablesInString(
template.docs[0].html,
variables,
)Passing Variables via Hooks
The recommended approach is to pass variables through hooks or service functions when sending emails. Here's an example pattern:
// hooks/emailService.ts
import { substitueVariablesInString } from '@flexiweb/email-templates/utils'
import type { Payload } from 'payload'
export async function sendEmail(
payload: Payload,
templateType: string,
recipient: { email: string; firstName: string; lastName: string },
additionalVariables: Record<string, unknown> = {},
) {
// Fetch the template
const templateResult = await payload.find({
collection: 'email-templates',
where: {
type: { equals: templateType },
enabled: { equals: true },
},
limit: 1,
})
if (templateResult.docs.length === 0) {
throw new Error(`Template "${templateType}" not found or disabled`)
}
const template = templateResult.docs[0]
// Prepare variables
const now = new Date()
const variables = {
recipient: {
firstName: recipient.firstName,
lastName: recipient.lastName,
fullName: `${recipient.firstName} ${recipient.lastName}`,
email: recipient.email,
},
handler: {
email: '[email protected]',
fullName: 'Support Team',
},
currentDate: now.toLocaleDateString(),
currentYear: now.getFullYear().toString(),
currentMonth: (now.getMonth() + 1).toString(),
currentDay: now.getDate().toString(),
...additionalVariables, // Merge any additional variables
}
// Render the template
const renderedHtml = substitueVariablesInString(template.html, variables)
// Send the email (using your email service)
await sendEmailWithService({
to: recipient.email,
subject: `Welcome ${recipient.firstName}!`,
html: renderedHtml,
})
}Using in Payload Hooks
import { AfterCreateHook } from 'payload'
export const sendWelcomeEmail: AfterCreateHook = async ({ doc, req }) => {
if (doc.collection === 'users' && doc.email) {
await sendEmail(req.payload, 'welcome-email', {
email: doc.email,
firstName: doc.firstName || 'User',
lastName: doc.lastName || '',
}, {
// Additional context-specific variables
signupDate: doc.createdAt,
accountType: doc.role,
})
}
}Variable Escaping
The plugin includes utilities for escaping and unescaping template tags:
import { escapeTags, unescapeTags } from '@flexiweb/email-templates/utils'
// Escape tags (useful for displaying template code)
const escaped = escapeTags('<%= it.name %>') // Returns: '<%= it.name %>'
// Unescape tags (done automatically during rendering)
const unescaped = unescapeTags('<%= it.name %>') // Returns: '<%= it.name %>'Custom Fields
RedirectToEditorButton
A custom field that provides a button to open the email template editor.
Usage:
This field is automatically included in the email templates collection. It appears when editing a template and provides a link to the visual editor.
Customization:
import { RedirectToEditorButtonField } from '@flexiweb/email-templates/fields'
// The field is already included, but you can customize it via overrides
flexiwebEmailTemplatesPlugin({
overrides: {
emailTemplates: {
fieldOverrides: {
// Customize other fields, but RedirectToEditorButton is handled automatically
},
},
},
})Translations
The plugin includes comprehensive translations for German and English.
Translation Keys
All translation keys follow the pattern: flexiweb-email-templates:{category}:{key}
Categories:
editor:*- Editor-related messagesfields:*- Field labels and messagesvalidation:*- Validation error messages
Using Translations
Translations are automatically merged when you use the plugin:
import { flexiwebEmailTemplatesPlugin } from '@flexiweb/email-templates'
export default buildConfig({
plugins: [flexiwebEmailTemplatesPlugin()],
// Translations are automatically added
})Extending Translations
You can extend or override translations in your Payload config:
export default buildConfig({
plugins: [flexiwebEmailTemplatesPlugin()],
i18n: {
translations: {
'flexiweb-email-templates:editor:saved': {
en: 'Template saved!',
de: 'Vorlage gespeichert!',
},
},
},
})Utilities
Template Variable Utilities
import {
getTemplateVariables,
substitueVariablesInString,
escapeTags,
unescapeTags,
} from '@flexiweb/email-templates/utils'getTemplateVariables(template)
Extracts the variables array from a template document.
const variables = getTemplateVariables(template)
// Returns: Array of { variable: string, description?: string }[]substitueVariablesInString(input, variables)
Renders a template string with variables using the Eta engine.
const rendered = substitueVariablesInString(
'<%= it.name %>',
{ name: 'John' }
)
// Returns: 'John'escapeTags(input)
Escapes Eta template tags for safe display.
const escaped = escapeTags('<%= it.name %>')
// Returns: '<%= it.name %>'unescapeTags(input)
Unescapes Eta template tags (automatically called during rendering).
const unescaped = unescapeTags('<%= it.name %>')
// Returns: '<%= it.name %>'Overrides & Customization
The plugin is fully customizable at every level.
Plugin-Level Overrides
flexiwebEmailTemplatesPlugin({
disabled: false, // Disable plugin functionality
overrides: {
emailTemplates: {
additionalFields: [], // Add custom fields
fieldOverrides: {
// Override field configurations
nameOverrides: {
label: { en: 'Template Name', de: 'Vorlagenname' },
},
typeOverrides: {
options: [
{ label: 'Welcome Email', value: 'welcome' },
{ label: 'Order Confirmation', value: 'order-confirmation' },
],
},
},
overrides: {
// Override collection configuration
admin: {
defaultColumns: ['name', 'type', 'enabled'],
},
},
},
},
})Field-Level Overrides
You can override any field property:
flexiwebEmailTemplatesPlugin({
overrides: {
emailTemplates: {
fieldOverrides: {
enabledOverrides: {
defaultValue: true,
label: { en: 'Active', de: 'Aktiv' },
},
mjmlOverrides: {
admin: {
position: 'main',
readOnly: false, // Allow direct editing (not recommended)
},
},
},
},
},
})Access Control Overrides
You can customize access rules by overriding the collection:
import { createEmailTemplatesAccess } from '@flexiweb/email-templates/collections/email-templates/access'
flexiwebEmailTemplatesPlugin({
overrides: {
emailTemplates: {
overrides: {
access: {
create: ({ req: { user } }) => {
// Custom access logic
return user?.role === 'admin'
},
},
},
},
},
})License
This software is provided free of charge for use, modification, and distribution by any individual or legal entity ("Licensee") whose total annual gross revenue does not exceed USD 250,000.
If the Licensee's annual gross revenue exceeds USD 250,000, a paid commercial license is required. For commercial licensing, contact the maintainer at [email protected].
See LICENSE.md for full license details.
Built with ❤️ for the PayloadCMS community
