const { SESClient, SendEmailCommand } = require("@aws-sdk/client-ses"); const fs = require("fs").promises; const path = require("path"); const { getAWSConfig } = require("../config/aws"); const { User } = require("../models"); class EmailService { constructor() { this.sesClient = null; this.initialized = false; this.templates = new Map(); } async initialize() { if (this.initialized) return; try { // Use centralized AWS configuration with credential profiles const awsConfig = getAWSConfig(); this.sesClient = new SESClient(awsConfig); await this.loadEmailTemplates(); this.initialized = true; console.log("SES Email Service initialized successfully"); } catch (error) { console.error("Failed to initialize SES Email Service:", error); throw error; } } async loadEmailTemplates() { const templatesDir = path.join(__dirname, "..", "templates", "emails"); try { const templateFiles = [ "conditionCheckReminder.html", "rentalConfirmation.html", "lateReturnCS.html", "damageReportCS.html", "lostItemCS.html", ]; for (const templateFile of templateFiles) { try { const templatePath = path.join(templatesDir, templateFile); const templateContent = await fs.readFile(templatePath, "utf-8"); const templateName = path.basename(templateFile, ".html"); this.templates.set(templateName, templateContent); } catch (error) { console.warn(`Template ${templateFile} not found, will use fallback`); } } console.log(`Loaded ${this.templates.size} email templates`); } catch (error) { console.warn("Templates directory not found, using fallback templates"); } } async sendEmail(to, subject, htmlContent, textContent = null) { if (!this.initialized) { await this.initialize(); } if (!process.env.EMAIL_ENABLED || process.env.EMAIL_ENABLED !== "true") { console.log("Email sending disabled in environment"); return { success: true, messageId: "disabled" }; } const params = { Source: process.env.SES_FROM_EMAIL, Destination: { ToAddresses: Array.isArray(to) ? to : [to], }, Message: { Subject: { Data: subject, Charset: "UTF-8", }, Body: { Html: { Data: htmlContent, Charset: "UTF-8", }, }, }, }; if (textContent) { params.Message.Body.Text = { Data: textContent, Charset: "UTF-8", }; } if (process.env.SES_REPLY_TO_EMAIL) { params.ReplyToAddresses = [process.env.SES_REPLY_TO_EMAIL]; } try { const command = new SendEmailCommand(params); const result = await this.sesClient.send(command); console.log( `Email sent successfully to ${to}, MessageId: ${result.MessageId}` ); return { success: true, messageId: result.MessageId }; } catch (error) { console.error("Failed to send email:", error); return { success: false, error: error.message }; } } renderTemplate(templateName, variables = {}) { let template = this.templates.get(templateName); if (!template) { template = this.getFallbackTemplate(templateName); } let rendered = template; Object.keys(variables).forEach((key) => { const regex = new RegExp(`{{${key}}}`, "g"); rendered = rendered.replace(regex, variables[key] || ""); }); return rendered; } getFallbackTemplate(templateName) { const baseTemplate = ` {{title}}
{{content}}
`; const templates = { conditionCheckReminder: baseTemplate.replace( "{{content}}", `

{{title}}

{{message}}

Rental Item: {{itemName}}

Deadline: {{deadline}}

Please complete this condition check as soon as possible to ensure proper documentation.

` ), rentalConfirmation: baseTemplate.replace( "{{content}}", `

{{title}}

{{message}}

Item: {{itemName}}

Rental Period: {{startDate}} to {{endDate}}

Thank you for using RentAll!

` ), }; return ( templates[templateName] || baseTemplate.replace( "{{content}}", `

{{title}}

{{message}}

` ) ); } async sendConditionCheckReminder(userEmail, notification, rental) { const variables = { title: notification.title, message: notification.message, itemName: rental?.item?.name || "Unknown Item", deadline: notification.metadata?.deadline ? new Date(notification.metadata.deadline).toLocaleDateString() : "Not specified", }; const htmlContent = this.renderTemplate( "conditionCheckReminder", variables ); return await this.sendEmail( userEmail, `RentAll: ${notification.title}`, htmlContent ); } async sendRentalConfirmation(userEmail, notification, rental) { const variables = { title: notification.title, message: notification.message, itemName: rental?.item?.name || "Unknown Item", startDate: rental?.startDateTime ? new Date(rental.startDateTime).toLocaleDateString() : "Not specified", endDate: rental?.endDateTime ? new Date(rental.endDateTime).toLocaleDateString() : "Not specified", }; const htmlContent = this.renderTemplate("rentalConfirmation", variables); return await this.sendEmail( userEmail, `RentAll: ${notification.title}`, htmlContent ); } async sendTemplateEmail(toEmail, subject, templateName, variables = {}) { const htmlContent = this.renderTemplate(templateName, variables); return await this.sendEmail(toEmail, subject, htmlContent); } async sendLateReturnToCustomerService(rental, lateCalculation) { try { // Get owner and renter details const owner = await User.findByPk(rental.ownerId); const renter = await User.findByPk(rental.renterId); if (!owner || !renter) { console.error("Owner or renter not found for late return notification"); return; } // Format dates const scheduledEnd = new Date(rental.endDateTime).toLocaleString(); const actualReturn = new Date( rental.actualReturnDateTime ).toLocaleString(); // Send email to customer service await this.sendTemplateEmail( process.env.CUSTOMER_SUPPORT_EMAIL, "Late Return Detected - Action Required", "lateReturnCS", { rentalId: rental.id, itemName: rental.item.name, ownerName: owner.name, ownerEmail: owner.email, renterName: renter.name, renterEmail: renter.email, scheduledEnd, actualReturn, hoursLate: lateCalculation.lateHours.toFixed(1), lateFee: lateCalculation.lateFee.toFixed(2), } ); console.log( `Late return notification sent to customer service for rental ${rental.id}` ); } catch (error) { console.error( "Failed to send late return notification to customer service:", error ); } } async sendDamageReportToCustomerService( rental, damageAssessment, lateCalculation = null ) { try { // Get owner and renter details const owner = await User.findByPk(rental.ownerId); const renter = await User.findByPk(rental.renterId); if (!owner || !renter) { console.error( "Owner or renter not found for damage report notification" ); return; } // Calculate total fees const damageFee = damageAssessment.feeCalculation.amount; const lateFee = lateCalculation?.lateFee || 0; const totalFees = damageFee + lateFee; // Determine fee type description let feeTypeDescription = ""; if (damageAssessment.feeCalculation.type === "repair") { feeTypeDescription = "Repair Cost"; } else if (damageAssessment.feeCalculation.type === "replacement") { feeTypeDescription = "Replacement Cost"; } else { feeTypeDescription = "Damage Assessment Fee"; } // Send email to customer service await this.sendTemplateEmail( process.env.CUSTOMER_SUPPORT_EMAIL, "Damage Report Filed - Action Required", "damageReportCS", { rentalId: rental.id, itemName: rental.item.name, ownerName: `${owner.firstName} ${owner.lastName}`, ownerEmail: owner.email, renterName: `${renter.firstName} ${renter.lastName}`, renterEmail: renter.email, damageDescription: damageAssessment.description, canBeFixed: damageAssessment.canBeFixed ? "Yes" : "No", repairCost: damageAssessment.repairCost ? damageAssessment.repairCost.toFixed(2) : "N/A", needsReplacement: damageAssessment.needsReplacement ? "Yes" : "No", replacementCost: damageAssessment.replacementCost ? damageAssessment.replacementCost.toFixed(2) : "N/A", feeTypeDescription, damageFee: damageFee.toFixed(2), lateFee: lateFee.toFixed(2), totalFees: totalFees.toFixed(2), hasProofOfOwnership: damageAssessment.proofOfOwnership && damageAssessment.proofOfOwnership.length > 0 ? "Yes" : "No", } ); console.log( `Damage report notification sent to customer service for rental ${rental.id}` ); } catch (error) { console.error( "Failed to send damage report notification to customer service:", error ); } } async sendLostItemToCustomerService(rental) { try { // Get owner and renter details const owner = await User.findByPk(rental.ownerId); const renter = await User.findByPk(rental.renterId); if (!owner || !renter) { console.error("Owner or renter not found for lost item notification"); return; } // Format dates const reportedAt = new Date(rental.itemLostReportedAt).toLocaleString(); const scheduledReturnDate = new Date(rental.endDateTime).toLocaleString(); // Send email to customer service await this.sendTemplateEmail( process.env.CUSTOMER_SUPPORT_EMAIL, "Lost Item Claim Filed - Action Required", "lostItemCS", { rentalId: rental.id, itemName: rental.item.name, ownerName: `${owner.firstName} ${owner.lastName}`, ownerEmail: owner.email, renterName: `${renter.firstName} ${renter.lastName}`, renterEmail: renter.email, reportedAt, scheduledReturnDate, replacementCost: parseFloat(rental.item.replacementCost).toFixed(2), } ); console.log( `Lost item notification sent to customer service for rental ${rental.id}` ); } catch (error) { console.error( "Failed to send lost item notification to customer service:", error ); } } async sendRentalConfirmationEmails(rental) { try { // Get owner and renter emails const owner = await User.findByPk(rental.ownerId, { attributes: ["email"], }); const renter = await User.findByPk(rental.renterId, { attributes: ["email"], }); // Create notification data for owner const ownerNotification = { type: "rental_confirmed", title: "Rental Confirmed", message: `Your "${rental.item.name}" has been confirmed for rental.`, rentalId: rental.id, userId: rental.ownerId, metadata: { rentalStart: rental.startDateTime }, }; // Create notification data for renter const renterNotification = { type: "rental_confirmed", title: "Rental Confirmed", message: `Your rental of "${rental.item.name}" has been confirmed.`, rentalId: rental.id, userId: rental.renterId, metadata: { rentalStart: rental.startDateTime }, }; // Send email to owner if (owner?.email) { await this.sendRentalConfirmation( owner.email, ownerNotification, rental ); console.log(`Rental confirmation email sent to owner: ${owner.email}`); } // Send email to renter if (renter?.email) { await this.sendRentalConfirmation( renter.email, renterNotification, rental ); console.log( `Rental confirmation email sent to renter: ${renter.email}` ); } } catch (error) { console.error("Error sending rental confirmation emails:", error); } } } module.exports = new EmailService();