-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #685 from hpcc-systems/yadhap/decoupling-notificat…
…ion-flow Decoupling Notification Flow
- Loading branch information
Showing
16 changed files
with
1,168 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
/* | ||
1. The send Email function treats emails as sent if the SMTP server accepts the email for delivery. | ||
2. It does not guarantee that the email will be delivered to the recipient's inbox. | ||
For example - if the user provided email is correctly formatted but not a valid email address, | ||
the SMTP server will accept the email for delivery but will not deliver it to the recipient's inbox. | ||
3. To Debug email delivery issues, first check notification_queue table. if it does not exists there check table that stores sent notification. | ||
Next application Logs and SMTP server logs if available. | ||
*/ | ||
|
||
//Packages imports | ||
const nodemailer = require("nodemailer"); | ||
|
||
//Local imports | ||
const logger = require("./logger"); | ||
|
||
// SMTP configuration | ||
const smtpConfig = { | ||
host: process.env.EMAIL_SMTP_HOST, | ||
port: process.env.EMAIL_PORT, | ||
secure: false, // use SSL, | ||
tls: { rejectUnauthorized: false }, | ||
sender: process.env.EMAIL_SENDER, | ||
timeout: 10000, // in milliseconds | ||
debug: true, // set debug to true to see debug logs | ||
}; | ||
|
||
|
||
//Create transporter | ||
const transporter = nodemailer.createTransport(smtpConfig); | ||
|
||
// Send email function | ||
const sendEmail = ({receiver, cc, subject, plainTextBody, htmlBody}) => { | ||
return new Promise((resolve, reject) => { | ||
const mailOptions = { | ||
from: smtpConfig.sender, | ||
to: receiver, | ||
cc: cc, | ||
subject: subject, | ||
text: plainTextBody ? plainTextBody : null, | ||
html: htmlBody ? htmlBody : null, | ||
}; | ||
transporter.sendMail(mailOptions, function (error, info) { | ||
if (error) { | ||
reject(error); | ||
} | ||
resolve(info); | ||
}); | ||
}); | ||
}; | ||
|
||
// Re-try options | ||
const retryOptions = { | ||
maxRetries: 3, | ||
retryDelays: [1, 2, 3], // in minutes - Exponential backoff strategy | ||
}; | ||
|
||
// Exports | ||
module.exports = { | ||
sendEmail, | ||
retryOptions, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
|
||
const path = require("path"); | ||
const logger = require("../config/logger"); | ||
const PROCESS_EMAIL_NOTIFICATIONS = path.join("notifications", "processEmailNotifications.js"); | ||
const PROCESS_TEAMS_NOTIFICATIONS = path.join("notifications", "processTeamsNotifications.js"); | ||
|
||
async function scheduleEmailNotificationProcessing() { | ||
try { | ||
let jobName = "email-notification-processing-" + new Date().getTime(); | ||
this.bree.add({ | ||
name: jobName, | ||
interval: "10s", // Make it 120 seconds in production | ||
path: path.join(__dirname, "..", "jobs", PROCESS_EMAIL_NOTIFICATIONS), | ||
worker: { | ||
workerData: { | ||
jobName: jobName, | ||
WORKER_CREATED_AT: Date.now(), | ||
}, | ||
}, | ||
}); | ||
|
||
this.bree.start(jobName); | ||
logger.info("🔔 E-MAIL NOTIFICATION PROCESSING STARTED ..."); | ||
} catch (err) { | ||
console.error(err); | ||
} | ||
} | ||
|
||
async function scheduleTeamsNotificationProcessing() { | ||
try { | ||
let jobName = "teams-notification-processing-" + new Date().getTime(); | ||
this.bree.add({ | ||
name: jobName, | ||
interval: "10s", // Make it 120 seconds in production | ||
path: path.join(__dirname, "..", "jobs", PROCESS_TEAMS_NOTIFICATIONS), | ||
worker: { | ||
workerData: { | ||
jobName: jobName, | ||
WORKER_CREATED_AT: Date.now(), | ||
}, | ||
}, | ||
}); | ||
|
||
this.bree.start(jobName); | ||
logger.info("🔔 TEAMS NOTIFICATION PROCESSING STARTED ..."); | ||
} catch (err) { | ||
console.error(err); | ||
} | ||
} | ||
|
||
|
||
|
||
module.exports = { | ||
scheduleEmailNotificationProcessing, | ||
scheduleTeamsNotificationProcessing, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
const path = require("path"); | ||
const ejs = require("ejs"); | ||
const fs = require("fs"); | ||
const models = require("../../models"); | ||
const logger = require("../../config/logger"); | ||
|
||
const NotificationQueue = models.notification_queue; | ||
const { retryOptions: { maxRetries, retryDelays } } = require("../../config/emailConfig"); | ||
|
||
// Renders HTML template for email notification | ||
const renderEmailBody = ({ notificationOrigin, emailData }) => { | ||
const templatePath = path.join( __dirname, "..", "..", "notificationTemplates","email", `${notificationOrigin}.ejs`); | ||
const template = fs.readFileSync(templatePath, "utf-8") | ||
return ejs.render(template, emailData); | ||
}; | ||
|
||
// Function to calculate the retryAfter time | ||
const calculateRetryAfter = ({ | ||
attemptCount, | ||
retryDelays, // Configs related to emails should not be passed as params | ||
maxRetries, | ||
currentDateTime, | ||
}) => { | ||
if (attemptCount === maxRetries - 1) { | ||
return null; | ||
} else { | ||
return new Date(currentDateTime + retryDelays[attemptCount] * 60000); | ||
} | ||
}; | ||
|
||
//Update notification queue on error | ||
async function updateNotificationQueueOnError({ notificationId, | ||
attemptCount, | ||
notification, | ||
error | ||
}) { | ||
try { | ||
await NotificationQueue.update( | ||
{ | ||
attemptCount: attemptCount + 1, | ||
failureMessage: { err: error.message, notification }, | ||
reTryAfter: calculateRetryAfter({ | ||
attemptCount, | ||
retryDelays: retryDelays, | ||
maxRetries: maxRetries, | ||
currentDateTime: Date.now(), | ||
}), | ||
}, | ||
{ where: { id: notificationId } } | ||
); | ||
} catch (updateError) { | ||
logger.error(updateError); | ||
} | ||
} | ||
|
||
|
||
module.exports = { | ||
renderEmailBody, | ||
calculateRetryAfter, | ||
updateNotificationQueueOnError, | ||
}; |
Oops, something went wrong.