"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var EmailService_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.EmailService = void 0; const common_1 = require("@nestjs/common"); const config_1 = require("@nestjs/config"); let EmailService = EmailService_1 = class EmailService { constructor(configService) { this.configService = configService; this.logger = new common_1.Logger(EmailService_1.name); this.isConfigured = false; } onModuleInit() { const emailConfig = this.configService.get('email'); this.provider = emailConfig?.provider || 'sendgrid'; this.fromEmail = emailConfig?.from || 'noreply@example.com'; this.fromName = emailConfig?.fromName || 'Template SaaS'; this.replyTo = emailConfig?.replyTo || ''; this.sendgridApiKey = emailConfig?.sendgridApiKey || ''; this.sesRegion = emailConfig?.sesRegion || 'us-east-1'; this.sesAccessKeyId = emailConfig?.sesAccessKeyId || ''; this.sesSecretAccessKey = emailConfig?.sesSecretAccessKey || ''; this.smtpHost = emailConfig?.smtpHost || ''; this.smtpPort = emailConfig?.smtpPort || 587; this.smtpUser = emailConfig?.smtpUser || ''; this.smtpPassword = emailConfig?.smtpPassword || ''; this.smtpSecure = emailConfig?.smtpSecure || false; this.isConfigured = this.checkConfiguration(); if (this.isConfigured) { this.logger.log(`Email service initialized with provider: ${this.provider}`); } else { this.logger.warn('Email service not configured - emails will be logged only'); } } checkConfiguration() { switch (this.provider) { case 'sendgrid': return !!this.sendgridApiKey; case 'ses': return !!(this.sesAccessKeyId && this.sesSecretAccessKey); case 'smtp': return !!(this.smtpHost && this.smtpUser && this.smtpPassword); default: return false; } } async sendEmail(dto) { this.logger.debug(`Sending email to ${dto.to.email}: ${dto.subject}`); if (!this.isConfigured) { this.logger.warn('Email not configured, logging email instead'); this.logEmail(dto); return { success: true, messageId: `mock-${Date.now()}`, provider: this.provider, }; } try { switch (this.provider) { case 'sendgrid': return await this.sendViaSendGrid(dto); case 'ses': return await this.sendViaSES(dto); case 'smtp': return await this.sendViaSMTP(dto); default: throw new Error(`Unknown email provider: ${this.provider}`); } } catch (error) { this.logger.error(`Failed to send email: ${error.message}`, error.stack); return { success: false, provider: this.provider, error: error.message, }; } } async sendTemplateEmail(dto) { const { templateKey, variables, ...emailData } = dto; const template = await this.getTemplate(templateKey); if (!template) { return { success: false, provider: this.provider, error: `Template not found: ${templateKey}`, }; } const renderedSubject = this.renderTemplate(template.subject, variables || {}); const renderedHtml = this.renderTemplate(template.html, variables || {}); const renderedText = template.text ? this.renderTemplate(template.text, variables || {}) : undefined; return this.sendEmail({ ...emailData, subject: renderedSubject, html: renderedHtml, text: renderedText, }); } async sendBulkEmails(emails) { const results = []; const batchSize = 10; for (let i = 0; i < emails.length; i += batchSize) { const batch = emails.slice(i, i + batchSize); const batchResults = await Promise.all(batch.map((email) => this.sendEmail(email))); results.push(...batchResults); } return results; } async sendViaSendGrid(dto) { const message = { personalizations: [ { to: [{ email: dto.to.email, name: dto.to.name }], ...(dto.cc?.length && { cc: dto.cc.map((c) => ({ email: c.email, name: c.name })), }), ...(dto.bcc?.length && { bcc: dto.bcc.map((b) => ({ email: b.email, name: b.name })), }), }, ], from: { email: this.fromEmail, name: this.fromName }, ...(this.replyTo && { reply_to: { email: this.replyTo } }), subject: dto.subject, content: [], }; if (dto.text) { message.content.push({ type: 'text/plain', value: dto.text }); } if (dto.html) { message.content.push({ type: 'text/html', value: dto.html }); } if (dto.attachments?.length) { message.attachments = dto.attachments.map((a) => ({ content: a.content, filename: a.filename, type: a.contentType, disposition: 'attachment', })); } const response = await fetch('https://api.sendgrid.com/v3/mail/send', { method: 'POST', headers: { 'Authorization': `Bearer ${this.sendgridApiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify(message), }); if (!response.ok) { const errorBody = await response.text(); throw new Error(`SendGrid error: ${response.status} - ${errorBody}`); } const messageId = response.headers.get('x-message-id') || `sg-${Date.now()}`; return { success: true, messageId, provider: 'sendgrid', }; } async sendViaSES(dto) { const endpoint = `https://email.${this.sesRegion}.amazonaws.com/v2/email/outbound-emails`; const body = { FromEmailAddress: `${this.fromName} <${this.fromEmail}>`, Destination: { ToAddresses: [`${dto.to.name || ''} <${dto.to.email}>`], ...(dto.cc?.length && { CcAddresses: dto.cc.map((c) => `${c.name || ''} <${c.email}>`), }), ...(dto.bcc?.length && { BccAddresses: dto.bcc.map((b) => `${b.name || ''} <${b.email}>`), }), }, Content: { Simple: { Subject: { Data: dto.subject }, Body: { ...(dto.text && { Text: { Data: dto.text } }), ...(dto.html && { Html: { Data: dto.html } }), }, }, }, ...(this.replyTo && { ReplyToAddresses: [this.replyTo] }), }; const timestamp = new Date().toISOString().replace(/[:-]|\.\d{3}/g, ''); const date = timestamp.slice(0, 8); const headers = { 'Content-Type': 'application/json', 'X-Amz-Date': timestamp, 'Host': `email.${this.sesRegion}.amazonaws.com`, }; this.logger.warn('AWS SES: Using simplified implementation - consider using @aws-sdk/client-sesv2'); const response = await fetch(endpoint, { method: 'POST', headers: { ...headers, 'X-Amz-Security-Token': '', }, body: JSON.stringify(body), }); if (!response.ok) { const errorBody = await response.text(); throw new Error(`SES error: ${response.status} - ${errorBody}`); } const result = await response.json(); return { success: true, messageId: result.MessageId || `ses-${Date.now()}`, provider: 'ses', }; } async sendViaSMTP(dto) { this.logger.warn('SMTP: Requires nodemailer package - install with: npm install nodemailer'); this.logEmail(dto); return { success: true, messageId: `smtp-${Date.now()}`, provider: 'smtp', }; } async getTemplate(templateKey) { const templates = { welcome: { subject: 'Welcome to {{appName}}!', html: `

Welcome, {{userName}}!

Thank you for joining {{appName}}. We're excited to have you on board.

Get started by exploring our features.

Best regards,
The {{appName}} Team

`, text: 'Welcome, {{userName}}! Thank you for joining {{appName}}.', }, password_reset: { subject: 'Reset your password - {{appName}}', html: `

Password Reset Request

Hi {{userName}},

We received a request to reset your password. Click the link below to proceed:

Reset Password

This link expires in {{expiresIn}}.

If you didn't request this, please ignore this email.

`, text: 'Hi {{userName}}, Reset your password here: {{resetLink}}', }, invitation: { subject: "You've been invited to {{tenantName}}", html: `

You're Invited!

{{inviterName}} has invited you to join {{tenantName}} on {{appName}}.

Accept Invitation

This invitation expires in {{expiresIn}}.

`, text: "You've been invited to {{tenantName}}. Accept here: {{inviteLink}}", }, notification: { subject: '{{title}}', html: `

{{title}}

{{message}}

{{#if actionUrl}}

{{actionText}}

{{/if}} `, text: '{{title}}: {{message}}', }, }; return templates[templateKey] || null; } renderTemplate(template, variables) { let result = template; for (const [key, value] of Object.entries(variables)) { const regex = new RegExp(`\\{\\{${key}\\}\\}`, 'g'); result = result.replace(regex, String(value ?? '')); } result = result.replace(/\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (_, condition, content) => { return variables[condition] ? content : ''; }); return result; } logEmail(dto) { this.logger.log('=== EMAIL (NOT SENT - NO PROVIDER CONFIGURED) ==='); this.logger.log(`To: ${dto.to.email}`); if (dto.cc?.length) { this.logger.log(`CC: ${dto.cc.map((c) => c.email).join(', ')}`); } if (dto.bcc?.length) { this.logger.log(`BCC: ${dto.bcc.map((b) => b.email).join(', ')}`); } this.logger.log(`Subject: ${dto.subject}`); this.logger.log(`Body (text): ${dto.text?.substring(0, 200) || '(none)'}`); this.logger.log(`Body (html): ${dto.html?.substring(0, 200) || '(none)'}`); this.logger.log('================================================'); } isEnabled() { return this.isConfigured; } getProvider() { return this.provider; } }; exports.EmailService = EmailService; exports.EmailService = EmailService = EmailService_1 = __decorate([ (0, common_1.Injectable)(), __metadata("design:paramtypes", [config_1.ConfigService]) ], EmailService); //# sourceMappingURL=email.service.js.map