rental confirmation looks less like spam
This commit is contained in:
@@ -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(/ /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}}",
|
||||
`
|
||||
<p>Hi {{recipientName}},</p>
|
||||
<h2>{{title}}</h2>
|
||||
<p>{{message}}</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 = {
|
||||
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(
|
||||
|
||||
Reference in New Issue
Block a user