rental confirmation looks less like spam

This commit is contained in:
jackiettran
2025-10-09 15:13:40 -04:00
parent 34c0ad2920
commit 513347e8b7
2 changed files with 78 additions and 23 deletions

View File

@@ -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(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
// Convert common HTML elements to text equivalents
.replace(/<br\s*\/?>/gi, '\n')
.replace(/<\/p>/gi, '\n\n')
.replace(/<\/div>/gi, '\n')
.replace(/<\/li>/gi, '\n')
.replace(/<\/h[1-6]>/gi, '\n\n')
.replace(/<li>/gi, '• ')
// Remove remaining HTML tags
.replace(/<[^>]+>/g, '')
// Decode HTML entities
.replace(/&nbsp;/g, ' ')
.replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"')
.replace(/&#39;/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) { async sendEmail(to, subject, htmlContent, textContent = null) {
if (!this.initialized) { if (!this.initialized) {
await this.initialize(); await this.initialize();
@@ -67,8 +106,18 @@ class EmailService {
return { success: true, messageId: "disabled" }; 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 = { const params = {
Source: process.env.SES_FROM_EMAIL, Source: source,
Destination: { Destination: {
ToAddresses: Array.isArray(to) ? to : [to], ToAddresses: Array.isArray(to) ? to : [to],
}, },
@@ -82,16 +131,13 @@ class EmailService {
Data: htmlContent, Data: htmlContent,
Charset: "UTF-8", Charset: "UTF-8",
}, },
}, Text: {
},
};
if (textContent) {
params.Message.Body.Text = {
Data: textContent, Data: textContent,
Charset: "UTF-8", Charset: "UTF-8",
},
},
},
}; };
}
if (process.env.SES_REPLY_TO_EMAIL) { if (process.env.SES_REPLY_TO_EMAIL) {
params.ReplyToAddresses = [process.env.SES_REPLY_TO_EMAIL]; params.ReplyToAddresses = [process.env.SES_REPLY_TO_EMAIL];
@@ -177,6 +223,7 @@ class EmailService {
rentalConfirmation: baseTemplate.replace( rentalConfirmation: baseTemplate.replace(
"{{content}}", "{{content}}",
` `
<p>Hi {{recipientName}},</p>
<h2>{{title}}</h2> <h2>{{title}}</h2>
<p>{{message}}</p> <p>{{message}}</p>
<p><strong>Item:</strong> {{itemName}}</p> <p><strong>Item:</strong> {{itemName}}</p>
@@ -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 = { const variables = {
recipientName: recipientName || "there",
title: notification.title, title: notification.title,
message: notification.message, message: notification.message,
itemName: rental?.item?.name || "Unknown Item", itemName: itemName,
startDate: rental?.startDateTime startDate: rental?.startDateTime
? new Date(rental.startDateTime).toLocaleDateString() ? new Date(rental.startDateTime).toLocaleDateString()
: "Not specified", : "Not specified",
@@ -235,9 +285,12 @@ class EmailService {
const htmlContent = this.renderTemplate("rentalConfirmation", variables); const htmlContent = this.renderTemplate("rentalConfirmation", variables);
// Use clear, transactional subject line with item name
const subject = `Rental Confirmation - ${itemName}`;
return await this.sendEmail( return await this.sendEmail(
userEmail, userEmail,
`RentAll: ${notification.title}`, subject,
htmlContent htmlContent
); );
} }
@@ -421,12 +474,12 @@ class EmailService {
}; };
try { try {
// Get owner and renter emails // Get owner and renter details
const owner = await User.findByPk(rental.ownerId, { const owner = await User.findByPk(rental.ownerId, {
attributes: ["email"], attributes: ["email", "firstName"],
}); });
const renter = await User.findByPk(rental.renterId, { const renter = await User.findByPk(rental.renterId, {
attributes: ["email"], attributes: ["email", "firstName"],
}); });
// Create notification data for owner // Create notification data for owner
@@ -455,7 +508,8 @@ class EmailService {
const ownerResult = await this.sendRentalConfirmation( const ownerResult = await this.sendRentalConfirmation(
owner.email, owner.email,
ownerNotification, ownerNotification,
rental rental,
owner.firstName
); );
if (ownerResult.success) { if (ownerResult.success) {
console.log( console.log(
@@ -482,7 +536,8 @@ class EmailService {
const renterResult = await this.sendRentalConfirmation( const renterResult = await this.sendRentalConfirmation(
renter.email, renter.email,
renterNotification, renterNotification,
rental rental,
renter.firstName
); );
if (renterResult.success) { if (renterResult.success) {
console.log( console.log(

View File

@@ -225,13 +225,14 @@
</div> </div>
<div class="content"> <div class="content">
<h1> {{title}}</h1> <p>Hi {{recipientName}},</p>
<h1>{{title}}</h1>
<p>{{message}}</p> <p>{{message}}</p>
<div class="success-box"> <div class="success-box">
<div class="icon">🎉</div> <p><strong>Confirmation:</strong> Your rental has been successfully confirmed.</p>
<p><strong>Great news!</strong> Your rental has been successfully confirmed and you're all set.</p>
</div> </div>
<h2>Rental Details</h2> <h2>Rental Details</h2>
@@ -250,8 +251,6 @@
</tr> </tr>
</table> </table>
<a href="#" class="button">View Rental Details</a>
<h2>What's next?</h2> <h2>What's next?</h2>
<ul> <ul>
<li><strong>Before pickup:</strong> You'll receive a reminder to take condition photos</li> <li><strong>Before pickup:</strong> You'll receive a reminder to take condition photos</li>
@@ -272,9 +271,10 @@
</div> </div>
<div class="footer"> <div class="footer">
<p><strong>RentAll</strong></p>
<p>This is a transactional email confirming your rental. You received this message because you have an active rental transaction on our platform.</p>
<p>If you have any questions, please contact our support team.</p>
<p>&copy; 2024 RentAll. All rights reserved.</p> <p>&copy; 2024 RentAll. All rights reserved.</p>
<p>You received this email because you have a confirmed rental on RentAll.</p>
<p>If you have any questions, please <a href="mailto:support@rentall.com">contact our support team</a>.</p>
</div> </div>
</div> </div>
</body> </body>