Files
smartmta/dist_ts/mail/core/classes.templatemanager.js

240 lines
17 KiB
JavaScript
Raw Normal View History

2026-02-10 15:54:09 +00:00
import * as plugins from '../../plugins.js';
import * as paths from '../../paths.js';
import { logger } from '../../logger.js';
import { Email } from './classes.email.js';
/**
* Template category definitions
*/
export var TemplateCategory;
(function (TemplateCategory) {
TemplateCategory["NOTIFICATION"] = "notification";
TemplateCategory["TRANSACTIONAL"] = "transactional";
TemplateCategory["MARKETING"] = "marketing";
TemplateCategory["SYSTEM"] = "system";
})(TemplateCategory || (TemplateCategory = {}));
/**
* Enhanced template manager using Email class for template rendering
*/
export class TemplateManager {
templates = new Map();
defaultConfig;
constructor(defaultConfig) {
// Set default configuration
this.defaultConfig = {
from: defaultConfig?.from || 'noreply@mail.lossless.com',
replyTo: defaultConfig?.replyTo,
footerHtml: defaultConfig?.footerHtml || '',
footerText: defaultConfig?.footerText || ''
};
// Initialize with built-in templates
this.registerBuiltinTemplates();
}
/**
* Register built-in email templates
*/
registerBuiltinTemplates() {
// Welcome email
this.registerTemplate({
id: 'welcome',
name: 'Welcome Email',
description: 'Sent to users when they first sign up',
from: this.defaultConfig.from,
subject: 'Welcome to {{serviceName}}!',
category: TemplateCategory.TRANSACTIONAL,
bodyHtml: `
<h1>Welcome, {{firstName}}!</h1>
<p>Thank you for joining {{serviceName}}. We're excited to have you on board.</p>
<p>To get started, <a href="{{accountUrl}}">visit your account</a>.</p>
`,
bodyText: `Welcome, {{firstName}}!
Thank you for joining {{serviceName}}. We're excited to have you on board.
To get started, visit your account: {{accountUrl}}
`,
sampleData: {
firstName: 'John',
accountUrl: 'https://example.com/account'
}
});
// Password reset
this.registerTemplate({
id: 'password-reset',
name: 'Password Reset',
description: 'Sent when a user requests a password reset',
from: this.defaultConfig.from,
subject: 'Password Reset Request',
category: TemplateCategory.TRANSACTIONAL,
bodyHtml: `
<h2>Password Reset Request</h2>
<p>You recently requested to reset your password. Click the link below to reset it:</p>
<p><a href="{{resetUrl}}">Reset Password</a></p>
<p>This link will expire in {{expiryHours}} hours.</p>
<p>If you didn't request a password reset, please ignore this email.</p>
`,
sampleData: {
resetUrl: 'https://example.com/reset-password?token=abc123',
expiryHours: 24
}
});
// System notification
this.registerTemplate({
id: 'system-notification',
name: 'System Notification',
description: 'General system notification template',
from: this.defaultConfig.from,
subject: '{{subject}}',
category: TemplateCategory.SYSTEM,
bodyHtml: `
<h2>{{title}}</h2>
<div>{{message}}</div>
`,
sampleData: {
subject: 'Important System Notification',
title: 'System Maintenance',
message: 'The system will be undergoing maintenance on Saturday from 2-4am UTC.'
}
});
}
/**
* Register a new email template
* @param template The email template to register
*/
registerTemplate(template) {
if (this.templates.has(template.id)) {
logger.log('warn', `Template with ID '${template.id}' already exists and will be overwritten`);
}
// Add footer to templates if configured
if (this.defaultConfig.footerHtml && template.bodyHtml) {
template.bodyHtml += this.defaultConfig.footerHtml;
}
if (this.defaultConfig.footerText && template.bodyText) {
template.bodyText += this.defaultConfig.footerText;
}
this.templates.set(template.id, template);
logger.log('info', `Registered email template: ${template.id}`);
}
/**
* Get an email template by ID
* @param templateId The template ID
* @returns The template or undefined if not found
*/
getTemplate(templateId) {
return this.templates.get(templateId);
}
/**
* List all available templates
* @param category Optional category filter
* @returns Array of email templates
*/
listTemplates(category) {
const templates = Array.from(this.templates.values());
if (category) {
return templates.filter(template => template.category === category);
}
return templates;
}
/**
* Create an Email instance from a template
* @param templateId The template ID
* @param context The template context data
* @returns A configured Email instance
*/
async createEmail(templateId, context) {
const template = this.getTemplate(templateId);
if (!template) {
throw new Error(`Template with ID '${templateId}' not found`);
}
// Build attachments array for Email
const attachments = [];
if (template.attachments && template.attachments.length > 0) {
for (const attachment of template.attachments) {
try {
const attachmentPath = plugins.path.isAbsolute(attachment.path)
? attachment.path
: plugins.path.join(paths.MtaAttachmentsDir, attachment.path);
// Read the file
const fileBuffer = await plugins.fs.promises.readFile(attachmentPath);
attachments.push({
filename: attachment.name,
content: fileBuffer,
contentType: attachment.contentType || 'application/octet-stream'
});
}
catch (error) {
logger.log('error', `Failed to add attachment '${attachment.name}': ${error.message}`);
}
}
}
// Create Email instance with template content
const emailOptions = {
from: template.from || this.defaultConfig.from,
subject: template.subject,
text: template.bodyText || '',
html: template.bodyHtml,
// Note: 'to' is intentionally omitted for templates
attachments,
variables: context || {}
};
return new Email(emailOptions);
}
/**
* Create and completely process an Email instance from a template
* @param templateId The template ID
* @param context The template context data
* @returns A complete, processed Email instance ready to send
*/
async prepareEmail(templateId, context = {}) {
const email = await this.createEmail(templateId, context);
// Email class processes variables when needed, no pre-compilation required
return email;
}
/**
* Create a MIME-formatted email from a template
* @param templateId The template ID
* @param context The template context data
* @returns A MIME-formatted email string
*/
async createMimeEmail(templateId, context = {}) {
const email = await this.prepareEmail(templateId, context);
return email.toRFC822String(context);
}
/**
* Load templates from a directory
* @param directory The directory containing template JSON files
*/
async loadTemplatesFromDirectory(directory) {
try {
// Ensure directory exists
if (!plugins.fs.existsSync(directory)) {
logger.log('error', `Template directory does not exist: ${directory}`);
return;
}
// Get all JSON files
const files = plugins.fs.readdirSync(directory)
.filter(file => file.endsWith('.json'));
for (const file of files) {
try {
const filePath = plugins.path.join(directory, file);
const content = plugins.fs.readFileSync(filePath, 'utf8');
const template = JSON.parse(content);
// Validate template
if (!template.id || !template.subject || (!template.bodyHtml && !template.bodyText)) {
logger.log('warn', `Invalid template in ${file}: missing required fields`);
continue;
}
this.registerTemplate(template);
}
catch (error) {
logger.log('error', `Error loading template from ${file}: ${error.message}`);
}
}
logger.log('info', `Loaded ${this.templates.size} email templates`);
}
catch (error) {
logger.log('error', `Failed to load templates from directory: ${error.message}`);
throw error;
}
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy50ZW1wbGF0ZW1hbmFnZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9tYWlsL2NvcmUvY2xhc3Nlcy50ZW1wbGF0ZW1hbmFnZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxrQkFBa0IsQ0FBQztBQUM1QyxPQUFPLEtBQUssS0FBSyxNQUFNLGdCQUFnQixDQUFDO0FBQ3hDLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUN6QyxPQUFPLEVBQUUsS0FBSyxFQUF3QyxNQUFNLG9CQUFvQixDQUFDO0FBNkJqRjs7R0FFRztBQUNILE1BQU0sQ0FBTixJQUFZLGdCQUtYO0FBTEQsV0FBWSxnQkFBZ0I7SUFDMUIsaURBQTZCLENBQUE7SUFDN0IsbURBQStCLENBQUE7SUFDL0IsMkNBQXVCLENBQUE7SUFDdkIscUNBQWlCLENBQUE7QUFDbkIsQ0FBQyxFQUxXLGdCQUFnQixLQUFoQixnQkFBZ0IsUUFLM0I7QUFFRDs7R0FFRztBQUNILE1BQU0sT0FBTyxlQUFlO0lBQ2xCLFNBQVMsR0FBZ0MsSUFBSSxHQUFHLEVBQUUsQ0FBQztJQUNuRCxhQUFhLENBS25CO0lBRUYsWUFBWSxhQUtYO1FBQ0MsNEJBQTRCO1FBQzVCLElBQUksQ0FBQyxhQUFhLEdBQUc7WUFDbkIsSUFBSSxFQUFFLGFBQWEsRUFBRSxJQUFJLElBQUksMkJBQTJCO1lBQ3hELE9BQU8sRUFBRSxhQUFhLEVBQUUsT0FBTztZQUMvQixVQUFVLEVBQUUsYUFBYSxFQUFFLFVBQVUsSUFBSSxFQUFFO1lBQzNDLFVBQVUsRUFBRSxhQUFhLEVBQUUsVUFBVSxJQUFJLEVBQUU7U0FDNUMsQ0FBQztRQUVGLHFDQUFxQztRQUNyQyxJQUFJLENBQUMsd0JBQXdCLEVBQUUsQ0FBQztJQUNsQyxDQUFDO0lBRUQ7O09BRUc7SUFDSyx3QkFBd0I7UUFDOUIsZ0JBQWdCO1FBQ2hCLElBQUksQ0FBQyxnQkFB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