feat: refactor email-worker to use HTML email templates
Replace inline HTML in email notifications with professional HTML templates
for better email client compatibility and consistent branding.
Changes:
- Created emailTemplates.ts utility for loading and rendering templates
- Template caching for performance
- Variable substitution with ${variable} syntax
- Warning logging for unreplaced variables
- Updated sendVerificationRequests to use email-validation template
- Variables: location.tenantName, ownerName, location.name, shareId
- Updated sendRentDueNotifications to use rent-due template
- Fetches user settings for owner name and currency
- Calculates rent due date from yearMonth and rentDueDay
- Formats rent amount (converts cents to display format)
- Variables: location.tenantName, location.name, rentDueDate,
rentAmount, currency, ownerName, shareId
- Updated sendUtilityBillsNotifications to use util-bills-due template
- Calculates total amount from all bills
- Fetches user settings for owner name and currency
- Variables: location.tenantName, location.name, totalAmount,
currency, ownerName, shareId
- Fixed ObjectId type mismatches in MongoDB operations
All emails now feature:
- Responsive 3-column layout
- rezije.app branding with logo
- Professional typography and spacing
- Unsubscribe links
- Email client compatible table-based layouts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
87
email-worker/src/lib/emailTemplates.ts
Normal file
87
email-worker/src/lib/emailTemplates.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { createLogger } from './logger';
|
||||
|
||||
const log = createLogger('email:templates');
|
||||
|
||||
// Cache for loaded templates
|
||||
const templateCache = new Map<string, string>();
|
||||
|
||||
/**
|
||||
* Template variable type for type-safe template rendering
|
||||
*/
|
||||
export type TemplateVariables = {
|
||||
[key: string]: string | number | undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Load an email template from the templates directory
|
||||
* @param templateName Name of the template file (without extension)
|
||||
* @param language Language code (default: 'en')
|
||||
* @returns Template content as string
|
||||
*/
|
||||
export function loadTemplate(templateName: string, language: string = 'en'): string {
|
||||
const cacheKey = `${templateName}--${language}`;
|
||||
|
||||
// Check cache first
|
||||
if (templateCache.has(cacheKey)) {
|
||||
log(`Using cached template: ${cacheKey}`);
|
||||
return templateCache.get(cacheKey)!;
|
||||
}
|
||||
|
||||
// Construct template file path
|
||||
const templateFileName = `email-template--${templateName}--${language}.html`;
|
||||
const templatePath = path.join(__dirname, '../../email-templates', templateFileName);
|
||||
|
||||
try {
|
||||
const content = fs.readFileSync(templatePath, 'utf-8');
|
||||
templateCache.set(cacheKey, content);
|
||||
log(`Loaded template: ${templateFileName}`);
|
||||
return content;
|
||||
} catch (error) {
|
||||
log(`Failed to load template ${templateFileName}: ${error}`);
|
||||
throw new Error(`Template not found: ${templateFileName}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a template by replacing variables
|
||||
* @param template Template content
|
||||
* @param variables Object with variable values
|
||||
* @returns Rendered HTML string
|
||||
*/
|
||||
export function renderTemplate(template: string, variables: TemplateVariables): string {
|
||||
let rendered = template;
|
||||
|
||||
// Replace all ${variable} occurrences
|
||||
for (const [key, value] of Object.entries(variables)) {
|
||||
if (value !== undefined) {
|
||||
const regex = new RegExp(`\\$\\{${key}\\}`, 'g');
|
||||
rendered = rendered.replace(regex, String(value));
|
||||
}
|
||||
}
|
||||
|
||||
// Log warning if there are unreplaced variables
|
||||
const unreplacedMatches = rendered.match(/\$\{[^}]+\}/g);
|
||||
if (unreplacedMatches) {
|
||||
log(`Warning: Unreplaced variables in template: ${unreplacedMatches.join(', ')}`);
|
||||
}
|
||||
|
||||
return rendered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and render an email template in one step
|
||||
* @param templateName Name of the template file (without extension)
|
||||
* @param variables Object with variable values
|
||||
* @param language Language code (default: 'en')
|
||||
* @returns Rendered HTML string
|
||||
*/
|
||||
export function loadAndRender(
|
||||
templateName: string,
|
||||
variables: TemplateVariables,
|
||||
language: string = 'en'
|
||||
): string {
|
||||
const template = loadTemplate(templateName, language);
|
||||
return renderTemplate(template, variables);
|
||||
}
|
||||
Reference in New Issue
Block a user