email refactor

This commit is contained in:
jackiettran
2025-11-14 17:36:35 -05:00
parent 629f0055a1
commit 3a6da3d47d
25 changed files with 3176 additions and 2219 deletions

View File

@@ -0,0 +1,71 @@
const EmailClient = require("../core/EmailClient");
const TemplateManager = require("../core/TemplateManager");
/**
* AlphaInvitationEmailService handles alpha program invitation emails
* This service is responsible for:
* - Sending alpha access invitation codes to new testers
*/
class AlphaInvitationEmailService {
constructor() {
this.emailClient = new EmailClient();
this.templateManager = new TemplateManager();
this.initialized = false;
}
/**
* Initialize the alpha invitation email service
* @returns {Promise<void>}
*/
async initialize() {
if (this.initialized) return;
await Promise.all([
this.emailClient.initialize(),
this.templateManager.initialize(),
]);
this.initialized = true;
console.log("Alpha Invitation Email Service initialized successfully");
}
/**
* Send alpha invitation email
* @param {string} email - Recipient's email address
* @param {string} code - Alpha access code
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
*/
async sendAlphaInvitation(email, code) {
if (!this.initialized) {
await this.initialize();
}
try {
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000";
const variables = {
code: code,
email: email,
frontendUrl: frontendUrl,
title: "Welcome to Alpha Testing!",
message: `You've been invited to join our exclusive alpha testing program. Use the code <strong>${code}</strong> to unlock access and be among the first to experience our platform.`,
};
const htmlContent = await this.templateManager.renderTemplate(
"alphaInvitationToUser",
variables
);
return await this.emailClient.sendEmail(
email,
"Your Alpha Access Code - RentAll",
htmlContent
);
} catch (error) {
console.error("Failed to send alpha invitation email:", error);
return { success: false, error: error.message };
}
}
}
module.exports = AlphaInvitationEmailService;

View File

@@ -0,0 +1,136 @@
const EmailClient = require("../core/EmailClient");
const TemplateManager = require("../core/TemplateManager");
/**
* AuthEmailService handles all authentication and account security related emails
* This service is responsible for:
* - Sending email verification links
* - Sending password reset links
* - Sending password changed confirmations
*/
class AuthEmailService {
constructor() {
this.emailClient = new EmailClient();
this.templateManager = new TemplateManager();
this.initialized = false;
}
/**
* Initialize the auth email service
* @returns {Promise<void>}
*/
async initialize() {
if (this.initialized) return;
await Promise.all([
this.emailClient.initialize(),
this.templateManager.initialize(),
]);
this.initialized = true;
console.log("Auth Email Service initialized successfully");
}
/**
* Send email verification email to new users
* @param {Object} user - User object
* @param {string} user.firstName - User's first name
* @param {string} user.email - User's email address
* @param {string} verificationToken - Email verification token
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
*/
async sendVerificationEmail(user, verificationToken) {
if (!this.initialized) {
await this.initialize();
}
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000";
const verificationUrl = `${frontendUrl}/verify-email?token=${verificationToken}`;
const variables = {
recipientName: user.firstName || "there",
verificationUrl: verificationUrl,
};
const htmlContent = await this.templateManager.renderTemplate(
"emailVerificationToUser",
variables
);
return await this.emailClient.sendEmail(
user.email,
"Verify Your Email - RentAll",
htmlContent
);
}
/**
* Send password reset email with reset link
* @param {Object} user - User object
* @param {string} user.firstName - User's first name
* @param {string} user.email - User's email address
* @param {string} resetToken - Password reset token
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
*/
async sendPasswordResetEmail(user, resetToken) {
if (!this.initialized) {
await this.initialize();
}
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000";
const resetUrl = `${frontendUrl}/reset-password?token=${resetToken}`;
const variables = {
recipientName: user.firstName || "there",
resetUrl: resetUrl,
};
const htmlContent = await this.templateManager.renderTemplate(
"passwordResetToUser",
variables
);
return await this.emailClient.sendEmail(
user.email,
"Reset Your Password - RentAll",
htmlContent
);
}
/**
* Send password changed confirmation email
* @param {Object} user - User object
* @param {string} user.firstName - User's first name
* @param {string} user.email - User's email address
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
*/
async sendPasswordChangedEmail(user) {
if (!this.initialized) {
await this.initialize();
}
const timestamp = new Date().toLocaleString("en-US", {
dateStyle: "long",
timeStyle: "short",
});
const variables = {
recipientName: user.firstName || "there",
email: user.email,
timestamp: timestamp,
};
const htmlContent = await this.templateManager.renderTemplate(
"passwordChangedToUser",
variables
);
return await this.emailClient.sendEmail(
user.email,
"Password Changed Successfully - RentAll",
htmlContent
);
}
}
module.exports = AuthEmailService;

View File

@@ -0,0 +1,299 @@
const EmailClient = require("../core/EmailClient");
const TemplateManager = require("../core/TemplateManager");
/**
* CustomerServiceEmailService handles all customer service alert emails
* This service is responsible for:
* - Sending late return notifications to CS team
* - Sending damage report notifications to CS team
* - Sending lost item notifications to CS team
*/
class CustomerServiceEmailService {
constructor() {
this.emailClient = new EmailClient();
this.templateManager = new TemplateManager();
this.initialized = false;
}
/**
* Initialize the customer service email service
* @returns {Promise<void>}
*/
async initialize() {
if (this.initialized) return;
await Promise.all([
this.emailClient.initialize(),
this.templateManager.initialize(),
]);
this.initialized = true;
console.log("Customer Service Email Service initialized successfully");
}
/**
* Send late return notification to customer service
* @param {Object} rental - Rental object
* @param {number} rental.id - Rental ID
* @param {Date} rental.endDateTime - Scheduled end date/time
* @param {Date} rental.actualReturnDateTime - Actual return date/time
* @param {Object} rental.item - Item object with name property
* @param {Object} owner - Owner user object
* @param {string} owner.firstName - Owner's first name
* @param {string} owner.lastName - Owner's last name
* @param {string} owner.email - Owner's email
* @param {Object} renter - Renter user object
* @param {string} renter.firstName - Renter's first name
* @param {string} renter.lastName - Renter's last name
* @param {string} renter.email - Renter's email
* @param {Object} lateCalculation - Late fee calculation
* @param {number} lateCalculation.lateHours - Hours late
* @param {number} lateCalculation.lateFee - Late fee amount
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
*/
async sendLateReturnToCustomerService(rental, owner, renter, lateCalculation) {
if (!this.initialized) {
await this.initialize();
}
try {
const csEmail = process.env.CUSTOMER_SUPPORT_EMAIL;
if (!csEmail) {
console.warn("No customer service email configured");
return { success: false, error: "No customer service email configured" };
}
// Format dates
const scheduledEnd = new Date(rental.endDateTime).toLocaleString();
const actualReturn = new Date(rental.actualReturnDateTime).toLocaleString();
const variables = {
rentalId: rental.id,
itemName: rental.item.name,
ownerName: `${owner.firstName} ${owner.lastName}`,
ownerEmail: owner.email,
renterName: `${renter.firstName} ${renter.lastName}`,
renterEmail: renter.email,
scheduledEnd,
actualReturn,
hoursLate: lateCalculation.lateHours.toFixed(1),
lateFee: lateCalculation.lateFee.toFixed(2),
};
const htmlContent = await this.templateManager.renderTemplate(
"lateReturnToCS",
variables
);
const result = await this.emailClient.sendEmail(
csEmail,
"Late Return Detected - Action Required",
htmlContent
);
if (result.success) {
console.log(
`Late return notification sent to customer service for rental ${rental.id}`
);
}
return result;
} catch (error) {
console.error(
"Failed to send late return notification to customer service:",
error
);
return { success: false, error: error.message };
}
}
/**
* Send damage report notification to customer service
* @param {Object} rental - Rental object
* @param {number} rental.id - Rental ID
* @param {Object} rental.item - Item object with name property
* @param {Object} owner - Owner user object
* @param {string} owner.firstName - Owner's first name
* @param {string} owner.lastName - Owner's last name
* @param {string} owner.email - Owner's email
* @param {Object} renter - Renter user object
* @param {string} renter.firstName - Renter's first name
* @param {string} renter.lastName - Renter's last name
* @param {string} renter.email - Renter's email
* @param {Object} damageAssessment - Damage assessment details
* @param {string} damageAssessment.description - Damage description
* @param {boolean} damageAssessment.canBeFixed - Whether item can be repaired
* @param {number} [damageAssessment.repairCost] - Repair cost if applicable
* @param {boolean} damageAssessment.needsReplacement - Whether item needs replacement
* @param {number} [damageAssessment.replacementCost] - Replacement cost if applicable
* @param {Object} damageAssessment.feeCalculation - Fee calculation details
* @param {string} damageAssessment.feeCalculation.type - Fee type (repair/replacement)
* @param {number} damageAssessment.feeCalculation.amount - Fee amount
* @param {Array} [damageAssessment.proofOfOwnership] - Proof of ownership documents
* @param {Object} [lateCalculation] - Late fee calculation (optional)
* @param {number} [lateCalculation.lateFee] - Late fee amount
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
*/
async sendDamageReportToCustomerService(
rental,
owner,
renter,
damageAssessment,
lateCalculation = null
) {
if (!this.initialized) {
await this.initialize();
}
try {
const csEmail = process.env.CUSTOMER_SUPPORT_EMAIL;
if (!csEmail) {
console.warn("No customer service email configured");
return { success: false, error: "No customer service email configured" };
}
// Calculate total fees (ensure numeric values)
const damageFee = parseFloat(damageAssessment.feeCalculation.amount) || 0;
const lateFee = parseFloat(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";
}
const variables = {
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",
};
const htmlContent = await this.templateManager.renderTemplate(
"damageReportToCS",
variables
);
const result = await this.emailClient.sendEmail(
csEmail,
"Damage Report Filed - Action Required",
htmlContent
);
if (result.success) {
console.log(
`Damage report notification sent to customer service for rental ${rental.id}`
);
}
return result;
} catch (error) {
console.error(
"Failed to send damage report notification to customer service:",
error
);
return { success: false, error: error.message };
}
}
/**
* Send lost item notification to customer service
* @param {Object} rental - Rental object
* @param {number} rental.id - Rental ID
* @param {Date} rental.endDateTime - Scheduled return date
* @param {Date} rental.itemLostReportedAt - When loss was reported
* @param {Object} rental.item - Item object
* @param {string} rental.item.name - Item name
* @param {number} rental.item.replacementCost - Item replacement cost
* @param {Object} owner - Owner user object
* @param {string} owner.firstName - Owner's first name
* @param {string} owner.lastName - Owner's last name
* @param {string} owner.email - Owner's email
* @param {Object} renter - Renter user object
* @param {string} renter.firstName - Renter's first name
* @param {string} renter.lastName - Renter's last name
* @param {string} renter.email - Renter's email
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
*/
async sendLostItemToCustomerService(rental, owner, renter) {
if (!this.initialized) {
await this.initialize();
}
try {
const csEmail = process.env.CUSTOMER_SUPPORT_EMAIL;
if (!csEmail) {
console.warn("No customer service email configured");
return { success: false, error: "No customer service email configured" };
}
// Format dates
const reportedAt = new Date(rental.itemLostReportedAt).toLocaleString();
const scheduledReturnDate = new Date(rental.endDateTime).toLocaleString();
const variables = {
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),
};
const htmlContent = await this.templateManager.renderTemplate(
"lostItemToCS",
variables
);
const result = await this.emailClient.sendEmail(
csEmail,
"Lost Item Claim Filed - Action Required",
htmlContent
);
if (result.success) {
console.log(
`Lost item notification sent to customer service for rental ${rental.id}`
);
}
return result;
} catch (error) {
console.error(
"Failed to send lost item notification to customer service:",
error
);
return { success: false, error: error.message };
}
}
}
module.exports = CustomerServiceEmailService;

View File

@@ -0,0 +1,131 @@
const EmailClient = require("../core/EmailClient");
const TemplateManager = require("../core/TemplateManager");
/**
* FeedbackEmailService handles all feedback-related email notifications
* This service is responsible for:
* - Sending feedback confirmation to users
* - Sending feedback notifications to administrators
*/
class FeedbackEmailService {
constructor() {
this.emailClient = new EmailClient();
this.templateManager = new TemplateManager();
this.initialized = false;
}
/**
* Initialize the feedback email service
* @returns {Promise<void>}
*/
async initialize() {
if (this.initialized) return;
await Promise.all([
this.emailClient.initialize(),
this.templateManager.initialize(),
]);
this.initialized = true;
console.log("Feedback Email Service initialized successfully");
}
/**
* Send feedback confirmation email to user
* @param {Object} user - User object
* @param {string} user.firstName - User's first name
* @param {string} user.email - User's email address
* @param {Object} feedback - Feedback object
* @param {string} feedback.feedbackText - The feedback content
* @param {Date} feedback.createdAt - Feedback submission timestamp
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
*/
async sendFeedbackConfirmation(user, feedback) {
if (!this.initialized) {
await this.initialize();
}
const submittedAt = new Date(feedback.createdAt).toLocaleString("en-US", {
dateStyle: "long",
timeStyle: "short",
});
const variables = {
userName: user.firstName || "there",
userEmail: user.email,
feedbackText: feedback.feedbackText,
submittedAt: submittedAt,
year: new Date().getFullYear(),
};
const htmlContent = await this.templateManager.renderTemplate(
"feedbackConfirmationToUser",
variables
);
return await this.emailClient.sendEmail(
user.email,
"Thank You for Your Feedback - RentAll",
htmlContent
);
}
/**
* Send feedback notification to admin
* @param {Object} user - User object who submitted feedback
* @param {string} user.firstName - User's first name
* @param {string} user.lastName - User's last name
* @param {string} user.email - User's email address
* @param {string} user.id - User's ID
* @param {Object} feedback - Feedback object
* @param {string} feedback.id - Feedback ID
* @param {string} feedback.feedbackText - The feedback content
* @param {string} [feedback.url] - URL where feedback was submitted
* @param {string} [feedback.userAgent] - User's browser user agent
* @param {Date} feedback.createdAt - Feedback submission timestamp
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
*/
async sendFeedbackNotificationToAdmin(user, feedback) {
if (!this.initialized) {
await this.initialize();
}
const adminEmail =
process.env.FEEDBACK_EMAIL || process.env.CUSTOMER_SUPPORT_EMAIL;
if (!adminEmail) {
console.warn("No admin email configured for feedback notifications");
return { success: false, error: "No admin email configured" };
}
const submittedAt = new Date(feedback.createdAt).toLocaleString("en-US", {
dateStyle: "long",
timeStyle: "short",
});
const variables = {
userName: `${user.firstName} ${user.lastName}`.trim() || "Unknown User",
userEmail: user.email,
userId: user.id,
feedbackText: feedback.feedbackText,
feedbackId: feedback.id,
url: feedback.url || "Not provided",
userAgent: feedback.userAgent || "Not provided",
submittedAt: submittedAt,
year: new Date().getFullYear(),
};
const htmlContent = await this.templateManager.renderTemplate(
"feedbackNotificationToAdmin",
variables
);
return await this.emailClient.sendEmail(
adminEmail,
`New Feedback from ${user.firstName} ${user.lastName}`,
htmlContent
);
}
}
module.exports = FeedbackEmailService;

View File

@@ -0,0 +1,318 @@
const EmailClient = require("../core/EmailClient");
const TemplateManager = require("../core/TemplateManager");
/**
* ForumEmailService handles all forum-related email notifications
* This service is responsible for:
* - Sending comment notifications to post authors
* - Sending reply notifications to comment authors
* - Sending answer accepted notifications
* - Sending thread activity notifications to participants
*/
class ForumEmailService {
constructor() {
this.emailClient = new EmailClient();
this.templateManager = new TemplateManager();
this.initialized = false;
}
/**
* Initialize the forum email service
* @returns {Promise<void>}
*/
async initialize() {
if (this.initialized) return;
await Promise.all([
this.emailClient.initialize(),
this.templateManager.initialize(),
]);
this.initialized = true;
console.log("Forum Email Service initialized successfully");
}
/**
* Send notification when someone comments on a post
* @param {Object} postAuthor - Post author user object
* @param {string} postAuthor.firstName - Post author's first name
* @param {string} postAuthor.email - Post author's email
* @param {Object} commenter - Commenter user object
* @param {string} commenter.firstName - Commenter's first name
* @param {string} commenter.lastName - Commenter's last name
* @param {Object} post - Forum post object
* @param {number} post.id - Post ID
* @param {string} post.title - Post title
* @param {Object} comment - Comment object
* @param {string} comment.content - Comment content
* @param {Date} comment.createdAt - Comment creation timestamp
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
*/
async sendForumCommentNotification(postAuthor, commenter, post, comment) {
if (!this.initialized) {
await this.initialize();
}
try {
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000";
const postUrl = `${frontendUrl}/forum/posts/${post.id}`;
const timestamp = new Date(comment.createdAt).toLocaleString("en-US", {
dateStyle: "medium",
timeStyle: "short",
});
const variables = {
postAuthorName: postAuthor.firstName || "there",
commenterName:
`${commenter.firstName} ${commenter.lastName}`.trim() || "Someone",
postTitle: post.title,
commentContent: comment.content,
postUrl: postUrl,
timestamp: timestamp,
};
const htmlContent = await this.templateManager.renderTemplate(
"forumCommentToPostAuthor",
variables
);
const subject = `${commenter.firstName} ${commenter.lastName} commented on your post`;
const result = await this.emailClient.sendEmail(
postAuthor.email,
subject,
htmlContent
);
if (result.success) {
console.log(
`Forum comment notification email sent to ${postAuthor.email}`
);
}
return result;
} catch (error) {
console.error("Failed to send forum comment notification email:", error);
return { success: false, error: error.message };
}
}
/**
* Send notification when someone replies to a comment
* @param {Object} commentAuthor - Original comment author user object
* @param {string} commentAuthor.firstName - Comment author's first name
* @param {string} commentAuthor.email - Comment author's email
* @param {Object} replier - Replier user object
* @param {string} replier.firstName - Replier's first name
* @param {string} replier.lastName - Replier's last name
* @param {Object} post - Forum post object
* @param {number} post.id - Post ID
* @param {string} post.title - Post title
* @param {Object} reply - Reply comment object
* @param {string} reply.content - Reply content
* @param {Date} reply.createdAt - Reply creation timestamp
* @param {Object} parentComment - Parent comment being replied to
* @param {string} parentComment.content - Parent comment content
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
*/
async sendForumReplyNotification(
commentAuthor,
replier,
post,
reply,
parentComment
) {
if (!this.initialized) {
await this.initialize();
}
try {
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000";
const postUrl = `${frontendUrl}/forum/posts/${post.id}`;
const timestamp = new Date(reply.createdAt).toLocaleString("en-US", {
dateStyle: "medium",
timeStyle: "short",
});
const variables = {
commentAuthorName: commentAuthor.firstName || "there",
replierName:
`${replier.firstName} ${replier.lastName}`.trim() || "Someone",
postTitle: post.title,
parentCommentContent: parentComment.content,
replyContent: reply.content,
postUrl: postUrl,
timestamp: timestamp,
};
const htmlContent = await this.templateManager.renderTemplate(
"forumReplyToCommentAuthor",
variables
);
const subject = `${replier.firstName} ${replier.lastName} replied to your comment`;
const result = await this.emailClient.sendEmail(
commentAuthor.email,
subject,
htmlContent
);
if (result.success) {
console.log(
`Forum reply notification email sent to ${commentAuthor.email}`
);
}
return result;
} catch (error) {
console.error("Failed to send forum reply notification email:", error);
return { success: false, error: error.message };
}
}
/**
* Send notification when a comment is marked as the accepted answer
* @param {Object} commentAuthor - Comment author user object
* @param {string} commentAuthor.firstName - Comment author's first name
* @param {string} commentAuthor.email - Comment author's email
* @param {Object} postAuthor - Post author user object who accepted the answer
* @param {string} postAuthor.firstName - Post author's first name
* @param {string} postAuthor.lastName - Post author's last name
* @param {Object} post - Forum post object
* @param {number} post.id - Post ID
* @param {string} post.title - Post title
* @param {Object} comment - Comment that was accepted as answer
* @param {string} comment.content - Comment content
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
*/
async sendForumAnswerAcceptedNotification(
commentAuthor,
postAuthor,
post,
comment
) {
if (!this.initialized) {
await this.initialize();
}
try {
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000";
const postUrl = `${frontendUrl}/forum/posts/${post.id}`;
const variables = {
commentAuthorName: commentAuthor.firstName || "there",
postAuthorName:
`${postAuthor.firstName} ${postAuthor.lastName}`.trim() || "Someone",
postTitle: post.title,
commentContent: comment.content,
postUrl: postUrl,
};
const htmlContent = await this.templateManager.renderTemplate(
"forumAnswerAcceptedToCommentAuthor",
variables
);
const subject = `Your comment was marked as the accepted answer!`;
const result = await this.emailClient.sendEmail(
commentAuthor.email,
subject,
htmlContent
);
if (result.success) {
console.log(
`Forum answer accepted notification email sent to ${commentAuthor.email}`
);
}
return result;
} catch (error) {
console.error(
"Failed to send forum answer accepted notification email:",
error
);
return { success: false, error: error.message };
}
}
/**
* Send notification to thread participants about new activity
* @param {Object} participant - Participant user object
* @param {string} participant.firstName - Participant's first name
* @param {string} participant.email - Participant's email
* @param {Object} commenter - User who posted new comment
* @param {string} commenter.firstName - Commenter's first name
* @param {string} commenter.lastName - Commenter's last name
* @param {Object} post - Forum post object
* @param {number} post.id - Post ID
* @param {string} post.title - Post title
* @param {Object} comment - New comment/activity
* @param {string} comment.content - Comment content
* @param {Date} comment.createdAt - Comment creation timestamp
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
*/
async sendForumThreadActivityNotification(
participant,
commenter,
post,
comment
) {
if (!this.initialized) {
await this.initialize();
}
try {
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000";
const postUrl = `${frontendUrl}/forum/posts/${post.id}`;
const timestamp = new Date(comment.createdAt).toLocaleString("en-US", {
dateStyle: "medium",
timeStyle: "short",
});
const variables = {
participantName: participant.firstName || "there",
commenterName:
`${commenter.firstName} ${commenter.lastName}`.trim() || "Someone",
postTitle: post.title,
commentContent: comment.content,
postUrl: postUrl,
timestamp: timestamp,
};
const htmlContent = await this.templateManager.renderTemplate(
"forumThreadActivityToParticipant",
variables
);
const subject = `New activity on a post you're following`;
const result = await this.emailClient.sendEmail(
participant.email,
subject,
htmlContent
);
if (result.success) {
console.log(
`Forum thread activity notification email sent to ${participant.email}`
);
}
return result;
} catch (error) {
console.error(
"Failed to send forum thread activity notification email:",
error
);
return { success: false, error: error.message };
}
}
}
module.exports = ForumEmailService;

View File

@@ -0,0 +1,97 @@
const EmailClient = require("../core/EmailClient");
const TemplateManager = require("../core/TemplateManager");
/**
* MessagingEmailService handles all messaging-related email notifications
* This service is responsible for:
* - Sending new message notifications to users
*/
class MessagingEmailService {
constructor() {
this.emailClient = new EmailClient();
this.templateManager = new TemplateManager();
this.initialized = false;
}
/**
* Initialize the messaging email service
* @returns {Promise<void>}
*/
async initialize() {
if (this.initialized) return;
await Promise.all([
this.emailClient.initialize(),
this.templateManager.initialize(),
]);
this.initialized = true;
console.log("Messaging Email Service initialized successfully");
}
/**
* Send new message notification email
* @param {Object} receiver - User object of the message receiver
* @param {string} receiver.firstName - Receiver's first name
* @param {string} receiver.email - Receiver's email address
* @param {Object} sender - User object of the message sender
* @param {string} sender.id - Sender's user ID
* @param {string} sender.firstName - Sender's first name
* @param {string} sender.lastName - Sender's last name
* @param {Object} message - Message object
* @param {string} message.subject - Message subject
* @param {string} message.content - Message content
* @param {Date} message.createdAt - Message creation timestamp
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
*/
async sendNewMessageNotification(receiver, sender, message) {
if (!this.initialized) {
await this.initialize();
}
try {
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000";
const conversationUrl = `${frontendUrl}/messages/conversations/${sender.id}`;
const timestamp = new Date(message.createdAt).toLocaleString("en-US", {
dateStyle: "medium",
timeStyle: "short",
});
const variables = {
recipientName: receiver.firstName || "there",
senderName: `${sender.firstName} ${sender.lastName}`.trim() || "A user",
subject: message.subject,
messageContent: message.content,
conversationUrl: conversationUrl,
timestamp: timestamp,
};
const htmlContent = await this.templateManager.renderTemplate(
"newMessageToUser",
variables
);
const subject = `New message from ${sender.firstName} ${sender.lastName}`;
const result = await this.emailClient.sendEmail(
receiver.email,
subject,
htmlContent
);
if (result.success) {
console.log(
`Message notification email sent to ${receiver.email} from ${sender.firstName} ${sender.lastName}`
);
}
return result;
} catch (error) {
console.error("Failed to send message notification email:", error);
return { success: false, error: error.message };
}
}
}
module.exports = MessagingEmailService;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,77 @@
const EmailClient = require("../core/EmailClient");
const TemplateManager = require("../core/TemplateManager");
/**
* RentalReminderEmailService handles rental reminder emails
* This service is responsible for:
* - Sending condition check reminders
*/
class RentalReminderEmailService {
constructor() {
this.emailClient = new EmailClient();
this.templateManager = new TemplateManager();
this.initialized = false;
}
/**
* Initialize the rental reminder email service
* @returns {Promise<void>}
*/
async initialize() {
if (this.initialized) return;
await Promise.all([
this.emailClient.initialize(),
this.templateManager.initialize(),
]);
this.initialized = true;
console.log("Rental Reminder Email Service initialized successfully");
}
/**
* Send condition check reminder email
* @param {string} userEmail - User's email address
* @param {Object} notification - Notification object
* @param {string} notification.title - Notification title
* @param {string} notification.message - Notification message
* @param {Object} notification.metadata - Notification metadata
* @param {string} notification.metadata.deadline - Condition check deadline
* @param {Object} rental - Rental object
* @param {Object} rental.item - Item object
* @param {string} rental.item.name - Item name
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
*/
async sendConditionCheckReminder(userEmail, notification, rental) {
if (!this.initialized) {
await this.initialize();
}
try {
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 = await this.templateManager.renderTemplate(
"conditionCheckReminderToUser",
variables
);
return await this.emailClient.sendEmail(
userEmail,
`RentAll: ${notification.title}`,
htmlContent
);
} catch (error) {
console.error("Failed to send condition check reminder:", error);
return { success: false, error: error.message };
}
}
}
module.exports = RentalReminderEmailService;

View File

@@ -0,0 +1,77 @@
const EmailClient = require("../core/EmailClient");
const TemplateManager = require("../core/TemplateManager");
/**
* UserEngagementEmailService handles user engagement emails
* This service is responsible for:
* - Sending first listing celebration emails
* - Other user engagement and milestone emails
*/
class UserEngagementEmailService {
constructor() {
this.emailClient = new EmailClient();
this.templateManager = new TemplateManager();
this.initialized = false;
}
/**
* Initialize the user engagement email service
* @returns {Promise<void>}
*/
async initialize() {
if (this.initialized) return;
await Promise.all([
this.emailClient.initialize(),
this.templateManager.initialize(),
]);
this.initialized = true;
console.log("User Engagement Email Service initialized successfully");
}
/**
* Send first listing celebration email to owner
* @param {Object} owner - Owner user object
* @param {string} owner.firstName - Owner's first name
* @param {string} owner.email - Owner's email address
* @param {Object} item - Item object
* @param {number} item.id - Item ID
* @param {string} item.name - Item name
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
*/
async sendFirstListingCelebrationEmail(owner, item) {
if (!this.initialized) {
await this.initialize();
}
try {
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000";
const variables = {
ownerName: owner.firstName || "there",
itemName: item.name,
itemId: item.id,
viewItemUrl: `${frontendUrl}/items/${item.id}`,
};
const htmlContent = await this.templateManager.renderTemplate(
"firstListingCelebrationToOwner",
variables
);
const subject = `Congratulations! Your first item is live on RentAll`;
return await this.emailClient.sendEmail(
owner.email,
subject,
htmlContent
);
} catch (error) {
console.error("Failed to send first listing celebration email:", error);
return { success: false, error: error.message };
}
}
}
module.exports = UserEngagementEmailService;