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) {
|
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(
|
||||||
|
|||||||
@@ -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>© 2024 RentAll. All rights reserved.</p>
|
<p>© 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>
|
||||||
|
|||||||
Reference in New Issue
Block a user