240 lines
17 KiB
JavaScript
240 lines
17 KiB
JavaScript
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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
|