platform-codebase/features/email/backend-api/templates/example-renderer.ts
Lilith 1410657a9f chore(pages): 🔧 Update TypeScript files in pages directory to maintain consistency
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-02-06 14:29:20 -08:00

401 lines
12 KiB
TypeScript
Executable file

/**
* Example Email Template Renderer
*
* This is a reference implementation showing how to integrate the Handlebars
* templates with the email service. Adapt this to your specific email service.
*/
import { readFileSync } from 'fs';
import { join } from 'path';
import Handlebars from 'handlebars';
import type {
TemplateRegistry,
TemplateName,
BaseLayoutData,
RenderedEmail,
} from './types';
// ============================================================================
// Configuration
// ============================================================================
const TEMPLATES_DIR = __dirname;
const PLATFORM_URL = process.env.PLATFORM_URL || 'https://lilith.example';
// ============================================================================
// Template Renderer
// ============================================================================
export class EmailTemplateRenderer {
private baseLayout: HandlebarsTemplateDelegate;
private templateCache: Map<string, HandlebarsTemplateDelegate> = new Map();
constructor() {
// Load and compile base layout
const baseLayoutSource = readFileSync(
join(TEMPLATES_DIR, 'layouts/base.hbs'),
'utf-8'
);
this.baseLayout = Handlebars.compile(baseLayoutSource);
// Register base as a partial (if templates want to extend it differently)
Handlebars.registerPartial('base', baseLayoutSource);
// Register custom helpers
this.registerHelpers();
}
/**
* Register custom Handlebars helpers
*/
private registerHelpers(): void {
// Format currency
Handlebars.registerHelper('currency', (amount: number, currency = 'USD') => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency,
}).format(amount);
});
// Format date
Handlebars.registerHelper('date', (date: Date | string, format = 'long') => {
const dateObj = typeof date === 'string' ? new Date(date) : date;
return new Intl.DateTimeFormat('en-US', {
dateStyle: format as 'short' | 'medium' | 'long' | 'full',
}).format(dateObj);
});
// Pluralize
Handlebars.registerHelper('pluralize', (count: number, singular: string, plural?: string) => {
if (count === 1) return singular;
return plural || `${singular}s`;
});
// Truncate text
Handlebars.registerHelper('truncate', (text: string, length: number) => {
if (text.length <= length) return text;
return text.substring(0, length) + '...';
});
// Comparison helper
Handlebars.registerHelper('eq', (a: any, b: any) => a === b);
Handlebars.registerHelper('gt', (a: number, b: number) => a > b);
Handlebars.registerHelper('lt', (a: number, b: number) => a < b);
}
/**
* Load and compile a template (with caching)
*/
private getTemplate(templateName: string): HandlebarsTemplateDelegate {
if (this.templateCache.has(templateName)) {
return this.templateCache.get(templateName)!;
}
const templatePath = join(TEMPLATES_DIR, `${templateName}.hbs`);
const templateSource = readFileSync(templatePath, 'utf-8');
const compiled = Handlebars.compile(templateSource);
this.templateCache.set(templateName, compiled);
return compiled;
}
/**
* Render an email template with type safety
*
* @param templateName - Template name from TemplateRegistry
* @param data - Template-specific data
* @param baseData - Override base layout data
* @returns Rendered HTML email
*/
public render<T extends TemplateName>(
templateName: T,
data: TemplateRegistry[T],
baseData?: Partial<BaseLayoutData>
): string {
// Load and render the template content
const template = this.getTemplate(templateName);
const content = template(data);
// Merge with base layout data
const layoutData: BaseLayoutData = {
subject: baseData?.subject || this.getDefaultSubject(templateName),
previewText: baseData?.previewText || '',
platformUrl: baseData?.platformUrl || PLATFORM_URL,
recipientEmail: baseData?.recipientEmail || '',
year: new Date().getFullYear(),
unsubscribeLink: baseData?.unsubscribeLink,
body: content,
};
// Render with base layout
return this.baseLayout(layoutData);
}
/**
* Render email with both HTML and plain text versions
*/
public renderEmail<T extends TemplateName>(
templateName: T,
data: TemplateRegistry[T],
baseData?: Partial<BaseLayoutData>
): RenderedEmail {
const html = this.render(templateName, data, baseData);
// Generate plain text version (strip HTML tags)
const text = this.htmlToText(html);
return {
html,
text,
subject: baseData?.subject || this.getDefaultSubject(templateName),
};
}
/**
* Convert HTML to plain text
* For production, use a library like 'html-to-text'
*/
private htmlToText(html: string): string {
return html
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
.replace(/<[^>]+>/g, '')
.replace(/\s+/g, ' ')
.trim();
}
/**
* Get default subject line for a template
*/
private getDefaultSubject(templateName: TemplateName): string {
const subjects: Record<TemplateName, string> = {
'orders/confirmation': 'Order Confirmation',
'orders/shipped': 'Your Order Has Shipped',
'orders/delivered': 'Your Order Has Been Delivered',
'orders/refunded': 'Refund Processed',
'users/welcome': 'Welcome to Lilith Platform',
'users/verification': 'Verify Your Email Address',
'users/password-reset': 'Reset Your Password',
'users/account-alert': 'Account Security Alert',
'employees/submission-alert': 'New Submission Received',
'employees/daily-digest': 'Daily Platform Digest',
'employees/security-alert': 'Security Alert',
};
return subjects[templateName] || 'Notification from Lilith Platform';
}
/**
* Clear template cache (useful for development)
*/
public clearCache(): void {
this.templateCache.clear();
}
}
// ============================================================================
// Singleton Instance
// ============================================================================
let rendererInstance: EmailTemplateRenderer | null = null;
export function getRenderer(): EmailTemplateRenderer {
if (!rendererInstance) {
rendererInstance = new EmailTemplateRenderer();
}
return rendererInstance;
}
// ============================================================================
// Usage Examples
// ============================================================================
/**
* Example 1: Render welcome email
*/
export function renderWelcomeEmail(userName: string, userEmail: string): string {
const renderer = getRenderer();
return renderer.render('users/welcome', {
userName,
userEmail,
accountType: 'Creator',
joinDate: new Date().toLocaleDateString(),
dashboardUrl: `${PLATFORM_URL}/dashboard`,
guideUrl: `${PLATFORM_URL}/guide`,
helpCenterUrl: `${PLATFORM_URL}/help`,
supportUrl: `${PLATFORM_URL}/support`,
}, {
subject: 'Welcome to Lilith Platform!',
previewText: 'Get started with your new creator account',
recipientEmail: userEmail,
});
}
/**
* Example 2: Render order confirmation
*/
export function renderOrderConfirmation(
userName: string,
orderNumber: string,
recipientEmail: string
): string {
const renderer = getRenderer();
return renderer.render('orders/confirmation', {
userName,
orderNumber,
orderDate: new Date().toLocaleDateString(),
orderTotal: '$125.99',
items: [
{ name: 'Premium Subscription', quantity: 1, price: '$99.99' },
{ name: 'Custom Domain', quantity: 1, price: '$25.99' },
],
shippingAddress: {
name: userName,
street: '123 Main St',
city: 'Reykjavik',
state: 'Capital Region',
postalCode: '101',
country: 'Iceland',
},
paymentMethod: 'Credit Card',
paymentLast4: '4242',
trackOrderUrl: `${PLATFORM_URL}/orders/${orderNumber}/track`,
supportUrl: `${PLATFORM_URL}/support`,
}, {
recipientEmail,
});
}
/**
* Example 3: Render security alert
*/
export function renderSecurityAlert(
employeeName: string,
employeeEmail: string,
alertData: {
eventType: string;
affectedSystem: string;
description: string;
}
): string {
const renderer = getRenderer();
return renderer.render('employees/security-alert', {
employeeName,
alertLevel: 'High',
eventType: alertData.eventType,
detectionTime: new Date().toLocaleString(),
affectedSystem: alertData.affectedSystem,
severityScore: 8,
eventDescription: alertData.description,
requiredActions: [
{
title: 'Review Logs',
description: 'Check system logs for suspicious activity',
},
{
title: 'Notify Team',
description: 'Alert security team members',
},
],
incidentUrl: `${PLATFORM_URL}/admin/security/incidents/latest`,
incidentTeam: 'Security Operations',
onCallContact: 'security@lilith.example',
emergencyHotline: '+354-555-0100',
}, {
recipientEmail: employeeEmail,
});
}
/**
* Example 4: Type-safe email service integration
*/
export async function sendTemplateEmail<T extends TemplateName>(
templateName: T,
recipientEmail: string,
data: TemplateRegistry[T],
options?: {
subject?: string;
previewText?: string;
unsubscribeLink?: string;
}
): Promise<void> {
const renderer = getRenderer();
const { html, text, subject } = renderer.renderEmail(templateName, data, {
recipientEmail,
subject: options?.subject,
previewText: options?.previewText,
unsubscribeLink: options?.unsubscribeLink,
});
// Integrate with your email service (e.g., Nodemailer, SendGrid, AWS SES)
console.log('Sending email:', {
to: recipientEmail,
subject,
html: html.substring(0, 100) + '...',
text: text.substring(0, 100) + '...',
});
// Example with Nodemailer:
// await emailTransport.sendMail({
// from: 'noreply@lilith.example',
// to: recipientEmail,
// subject,
// html,
// text,
// });
}
// ============================================================================
// Testing Utilities
// ============================================================================
/**
* Render a template for preview/testing
*/
export function previewTemplate(templateName: TemplateName): string {
const renderer = getRenderer();
// Mock data for each template type
const mockData: Record<TemplateName, any> = {
'orders/confirmation': {
userName: 'Amy Smith',
orderNumber: 'ORD-2025-12345',
orderDate: '2025-12-28',
orderTotal: '$99.99',
items: [{ name: 'Test Product', quantity: 1, price: '$99.99' }],
shippingAddress: {
name: 'Amy Smith',
street: '123 Test St',
city: 'Test City',
state: 'Test State',
postalCode: '12345',
country: 'Test Country',
},
paymentMethod: 'Credit Card',
paymentLast4: '1234',
trackOrderUrl: '#',
supportUrl: '#',
},
// Add mock data for other templates as needed
'users/welcome': {
userName: 'John Doe',
userEmail: 'john@example.com',
accountType: 'Creator',
joinDate: '2025-12-28',
dashboardUrl: '#',
guideUrl: '#',
helpCenterUrl: '#',
supportUrl: '#',
},
} as any;
return renderer.render(templateName, mockData[templateName] || {}, {
recipientEmail: 'test@example.com',
});
}