UNPKG

@454creative/easy-email

Version:

A framework-agnostic email service library for Node.js with observability and monitoring

143 lines (121 loc) 5.28 kB
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; } }