template-saas/apps/backend/dist/modules/email/services/email.service.js
rckrdmrd 50a821a415
Some checks failed
CI / Backend CI (push) Has been cancelled
CI / Frontend CI (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / CI Summary (push) Has been cancelled
[SIMCO-V38] feat: Actualizar a SIMCO v3.8.0
- HERENCIA-SIMCO.md actualizado con directivas v3.7 y v3.8
- Actualizaciones de configuracion

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 08:53:08 -06:00

317 lines
13 KiB
JavaScript

"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: `
<h1>Welcome, {{userName}}!</h1>
<p>Thank you for joining {{appName}}. We're excited to have you on board.</p>
<p>Get started by exploring our features.</p>
<p>Best regards,<br/>The {{appName}} Team</p>
`,
text: 'Welcome, {{userName}}! Thank you for joining {{appName}}.',
},
password_reset: {
subject: 'Reset your password - {{appName}}',
html: `
<h1>Password Reset Request</h1>
<p>Hi {{userName}},</p>
<p>We received a request to reset your password. Click the link below to proceed:</p>
<p><a href="{{resetLink}}">Reset Password</a></p>
<p>This link expires in {{expiresIn}}.</p>
<p>If you didn't request this, please ignore this email.</p>
`,
text: 'Hi {{userName}}, Reset your password here: {{resetLink}}',
},
invitation: {
subject: "You've been invited to {{tenantName}}",
html: `
<h1>You're Invited!</h1>
<p>{{inviterName}} has invited you to join {{tenantName}} on {{appName}}.</p>
<p><a href="{{inviteLink}}">Accept Invitation</a></p>
<p>This invitation expires in {{expiresIn}}.</p>
`,
text: "You've been invited to {{tenantName}}. Accept here: {{inviteLink}}",
},
notification: {
subject: '{{title}}',
html: `
<h2>{{title}}</h2>
<p>{{message}}</p>
{{#if actionUrl}}
<p><a href="{{actionUrl}}">{{actionText}}</a></p>
{{/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