@454creative/easy-email
Version:
A framework-agnostic email service library for Node.js with observability and monitoring
143 lines (121 loc) • 5.28 kB
text/typescript
import { ITemplateService, TemplateInfo, TemplateValidationResult } from '../../interfaces/template.interface';
import { EmailProviderType } from '../../interfaces/email-options.interface';
import { SendGridConfig } from '../../interfaces/email-options.interface';
import { Logger } from '../../utils/logger';
import { TemplateError, InvalidTemplateError } from '../../errors';
export class SendGridTemplateService implements ITemplateService {
private logger: Logger;
constructor(_config: SendGridConfig) {
this.logger = Logger.getInstance();
}
async renderTemplate(
templateId: string,
data: Record<string, any>,
provider: EmailProviderType
): Promise<string> {
if (provider !== EmailProviderType.SENDGRID) {
throw new TemplateError(`Provider ${provider} not supported by SendGridTemplateService`);
}
try {
this.logger.debug(`Rendering SendGrid template: ${templateId}`, { data });
// In a real implementation, you would call SendGrid's template API
// For now, we'll simulate template rendering
const template = await this.getTemplate(templateId);
const renderedContent = this.renderWithData(template, data);
this.logger.debug(`SendGrid template rendered successfully: ${templateId}`);
return renderedContent;
} catch (error) {
this.logger.error(`Failed to render SendGrid template: ${templateId}`, { error });
throw new TemplateError(`Failed to render SendGrid template: ${templateId}`, { error });
}
}
async validateTemplate(templateId: string, provider: EmailProviderType): Promise<boolean> {
if (provider !== EmailProviderType.SENDGRID) {
return false;
}
try {
await this.getTemplate(templateId);
return true;
} catch {
return false;
}
}
async getTemplateInfo(templateId: string, provider: EmailProviderType): Promise<TemplateInfo> {
if (provider !== EmailProviderType.SENDGRID) {
throw new TemplateError(`Provider ${provider} not supported by SendGridTemplateService`);
}
try {
const template = await this.getTemplate(templateId);
return {
id: templateId,
name: template.name || templateId,
version: template.version || '1.0.0',
variables: template.variables || [],
lastModified: template.updated_at ? new Date(template.updated_at) : new Date(),
provider: EmailProviderType.SENDGRID
};
} catch (error) {
throw new TemplateError(`Failed to get template info: ${templateId}`, { error });
}
}
async validateTemplateWithDetails(templateId: string, data: Record<string, any>): Promise<TemplateValidationResult> {
try {
const template = await this.getTemplate(templateId);
const templateVariables = template.variables || [];
const providedVariables = Object.keys(data);
const missingVariables = templateVariables.filter((v: string) => !providedVariables.includes(v));
const warnings = providedVariables.filter((v: string) => !templateVariables.includes(v));
return {
isValid: missingVariables.length === 0,
errors: missingVariables.length > 0 ? [`Missing required variables: ${missingVariables.join(', ')}`] : [],
warnings: warnings.length > 0 ? [`Unused variables provided: ${warnings.join(', ')}`] : [],
variables: templateVariables,
missingVariables
};
} catch (error) {
return {
isValid: false,
errors: [`Template not found or invalid: ${templateId}`],
warnings: [],
variables: [],
missingVariables: []
};
}
}
private async getTemplate(templateId: string): Promise<any> {
// In a real implementation, this would call SendGrid's template API
// For now, we'll return a mock template structure
if (!templateId.startsWith('d-')) {
throw new InvalidTemplateError(`Invalid SendGrid template ID format: ${templateId}`);
}
// Mock template data - in production this would come from SendGrid API
return {
id: templateId,
name: `Template ${templateId}`,
version: '1.0.0',
variables: ['name', 'activationLink', 'email'],
updated_at: new Date().toISOString(),
content: {
html: '<h1>Hello {{name}}!</h1><p>Click here: {{activationLink}}</p>',
text: 'Hello {{name}}! Click here: {{activationLink}}'
}
};
}
private renderWithData(template: any, data: Record<string, any>): string {
// Simple template rendering - in production you might use a more sophisticated engine
let content = template.content.html || template.content.text || '';
// Replace variables with data
Object.entries(data).forEach(([key, value]) => {
const regex = new RegExp(`{{${key}}}`, 'g');
content = content.replace(regex, String(value));
});
// Replace any remaining variables with empty strings
const remainingVariables = content.match(/\{\{([^}]+)\}\}/g) || [];
remainingVariables.forEach((variable: string) => {
const key = variable.slice(2, -2); // Remove {{ }}
const regex = new RegExp(`{{${key}}}`, 'g');
content = content.replace(regex, '');
});
return content;
}
}