From 513347e8b7c006eee95ac607fc9be99b07b322dd Mon Sep 17 00:00:00 2001 From: jackiettran <41605212+jackiettran@users.noreply.github.com> Date: Thu, 9 Oct 2025 15:13:40 -0400 Subject: [PATCH] rental confirmation looks less like spam --- backend/services/emailService.js | 87 +++++++++++++++---- .../templates/emails/rentalConfirmation.html | 14 +-- 2 files changed, 78 insertions(+), 23 deletions(-) diff --git a/backend/services/emailService.js b/backend/services/emailService.js index a99cc34..f66fa91 100644 --- a/backend/services/emailService.js +++ b/backend/services/emailService.js @@ -57,6 +57,45 @@ class EmailService { } } + /** + * Convert HTML to plain text for email fallback + * Strips HTML tags and formats content for plain text email clients + */ + htmlToPlainText(html) { + return html + // Remove style and script tags and their content + .replace(/]*>[\s\S]*?<\/style>/gi, '') + .replace(/]*>[\s\S]*?<\/script>/gi, '') + // Convert common HTML elements to text equivalents + .replace(//gi, '\n') + .replace(/<\/p>/gi, '\n\n') + .replace(/<\/div>/gi, '\n') + .replace(/<\/li>/gi, '\n') + .replace(/<\/h[1-6]>/gi, '\n\n') + .replace(/
  • /gi, '• ') + // Remove remaining HTML tags + .replace(/<[^>]+>/g, '') + // Decode HTML entities + .replace(/ /g, ' ') + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, "'") + // Remove emojis and special characters that don't render well in plain text + .replace(/[\u{1F600}-\u{1F64F}]/gu, '') // Emoticons + .replace(/[\u{1F300}-\u{1F5FF}]/gu, '') // Misc Symbols and Pictographs + .replace(/[\u{1F680}-\u{1F6FF}]/gu, '') // Transport and Map + .replace(/[\u{2600}-\u{26FF}]/gu, '') // Misc symbols + .replace(/[\u{2700}-\u{27BF}]/gu, '') // Dingbats + .replace(/[\u{FE00}-\u{FE0F}]/gu, '') // Variation Selectors + .replace(/[\u{1F900}-\u{1F9FF}]/gu, '') // Supplemental Symbols and Pictographs + .replace(/[\u{1FA70}-\u{1FAFF}]/gu, '') // Symbols and Pictographs Extended-A + // Clean up excessive whitespace + .replace(/\n\s*\n\s*\n/g, '\n\n') + .trim(); + } + async sendEmail(to, subject, htmlContent, textContent = null) { if (!this.initialized) { await this.initialize(); @@ -67,8 +106,18 @@ class EmailService { return { success: true, messageId: "disabled" }; } + // Auto-generate plain text from HTML if not provided + if (!textContent) { + textContent = this.htmlToPlainText(htmlContent); + } + + // Use friendly sender name format for better recognition + const fromName = process.env.SES_FROM_NAME || "RentAll"; + const fromEmail = process.env.SES_FROM_EMAIL; + const source = `${fromName} <${fromEmail}>`; + const params = { - Source: process.env.SES_FROM_EMAIL, + Source: source, Destination: { ToAddresses: Array.isArray(to) ? to : [to], }, @@ -82,17 +131,14 @@ class EmailService { Data: htmlContent, Charset: "UTF-8", }, + Text: { + Data: textContent, + 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]; } @@ -177,6 +223,7 @@ class EmailService { rentalConfirmation: baseTemplate.replace( "{{content}}", ` +

    Hi {{recipientName}},

    {{title}}

    {{message}}

    Item: {{itemName}}

    @@ -220,11 +267,14 @@ class EmailService { ); } - async sendRentalConfirmation(userEmail, notification, rental) { + async sendRentalConfirmation(userEmail, notification, rental, recipientName = null) { + const itemName = rental?.item?.name || "Unknown Item"; + const variables = { + recipientName: recipientName || "there", title: notification.title, message: notification.message, - itemName: rental?.item?.name || "Unknown Item", + itemName: itemName, startDate: rental?.startDateTime ? new Date(rental.startDateTime).toLocaleDateString() : "Not specified", @@ -235,9 +285,12 @@ class EmailService { const htmlContent = this.renderTemplate("rentalConfirmation", variables); + // Use clear, transactional subject line with item name + const subject = `Rental Confirmation - ${itemName}`; + return await this.sendEmail( userEmail, - `RentAll: ${notification.title}`, + subject, htmlContent ); } @@ -421,12 +474,12 @@ class EmailService { }; try { - // Get owner and renter emails + // Get owner and renter details const owner = await User.findByPk(rental.ownerId, { - attributes: ["email"], + attributes: ["email", "firstName"], }); const renter = await User.findByPk(rental.renterId, { - attributes: ["email"], + attributes: ["email", "firstName"], }); // Create notification data for owner @@ -455,7 +508,8 @@ class EmailService { const ownerResult = await this.sendRentalConfirmation( owner.email, ownerNotification, - rental + rental, + owner.firstName ); if (ownerResult.success) { console.log( @@ -482,7 +536,8 @@ class EmailService { const renterResult = await this.sendRentalConfirmation( renter.email, renterNotification, - rental + rental, + renter.firstName ); if (renterResult.success) { console.log( diff --git a/backend/templates/emails/rentalConfirmation.html b/backend/templates/emails/rentalConfirmation.html index 9505fed..70ce7b6 100644 --- a/backend/templates/emails/rentalConfirmation.html +++ b/backend/templates/emails/rentalConfirmation.html @@ -225,13 +225,14 @@
    -

    ✅ {{title}}

    +

    Hi {{recipientName}},

    + +

    {{title}}

    {{message}}

    -
    🎉
    -

    Great news! Your rental has been successfully confirmed and you're all set.

    +

    Confirmation: Your rental has been successfully confirmed.

    Rental Details

    @@ -250,8 +251,6 @@ - View Rental Details -

    What's next?

    • Before pickup: You'll receive a reminder to take condition photos
    • @@ -272,9 +271,10 @@