const path = require("path"); require("dotenv").config({ path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); let nodemailer = require("nodemailer"); let aws = require("@aws-sdk/client-ses"); let { defaultProvider } = require("@aws-sdk/credential-provider-node"); const InstanceManager = require("../utils/instanceMgr").default; const logger = require("../utils/logger"); const client = require("../graphql-client/graphql-client").client; const queries = require("../graphql-client/queries"); const generateEmailTemplate = require("./generateTemplate"); const moment = require("moment"); const { UPDATE_TASKS_REMIND_AT_SENT } = require("../graphql-client/queries"); const ses = new aws.SES({ apiVersion: "latest", defaultProvider, region: InstanceManager({ imex: "ca-central-1", rome: "us-east-2" }) }); const transporter = nodemailer.createTransport({ SES: { ses, aws }, sendingRate: 40 // 40 emails per second. }); const fromEmails = InstanceManager({ imex: `ImEX Online `, rome: `Rome Online `, promanager: `ProManager ` }); /** * Format the date for the email. * @param date * @returns {string|string} */ const formatDate = (date) => { return date ? `| Due on ${moment(date).format("MM/DD/YYYY")}` : ""; }; /** * Generate the email template arguments. * @param title * @param createdBy * @param dueDate * @param taskId * @returns {{header, body: string, subHeader: string}} */ const generateTemplateArgs = (title, createdBy, dueDate, taskId) => { return { header: title, subHeader: `Assigned by ${createdBy} ${formatDate(dueDate)}`, body: `Please sign in to your account to view the Task details.` }; }; /** * Send the email. * @param type * @param to * @param subject * @param html * @param taskIds * @param successCallback */ const sendMail = (type, to, subject, html, taskIds, successCallback) => { // Push next messages to Nodemailer transporter.once("idle", () => { if (transporter.isIdle()) { transporter.sendMail( { from: fromEmails, to, subject, html }, (error, info) => { if (info) { if (typeof successCallback === "function" && taskIds && taskIds.length) { successCallback(taskIds); } } else { logger.log(`task-${type}-email-failure`, "error", null, null, error); } } ); } }); }; /** * Send an email to the assigned user. * @param req * @param res * @returns {Promise<*>} */ const taskAssignedEmail = async (req, res) => { // We have no event Data, bail if (!req?.payload?.event?.data?.new) { return res.status(400).json({ message: "No data in the event payload" }); } const { new: newTask } = req.payload.event.data; // This is not a new task, but a reassignment. const dirty = req.payload.event.data?.old && req.payload.event.data?.old?.assigned_to; sendMail( "assigned", newTask.assigned_to, `A Task has been ${dirty ? "reassigned" : "created"} for you - ${newTask.title}`, generateEmailTemplate(generateTemplateArgs(newTask.title, newTask.created_by, newTask.due_date, newTask.id)) ); // We return success regardless because we don't want to block the event trigger. res.status(200).json({ success: true }); }; /** * Send an email to remind the user of their tasks. * @param req * @param res * @returns {Promise<*>} */ const tasksRemindEmail = async (req, res) => { try { const tasksRequest = await client.request(queries.QUERY_REMIND_TASKS, { time: moment().add(1, "minutes").toISOString() }); // No tasks present in the database, bail. if (!tasksRequest?.tasks || !tasksRequest?.tasks.length) { return res.status(200).json({ message: "No tasks to remind" }); } // Group tasks by assigned_to, to avoid sending multiple emails to the same recipient. const groupedTasks = tasksRequest.tasks.reduce((acc, task) => { const key = task.assigned_to; if (!acc[key]) { acc[key] = []; } acc[key].push(task); return acc; }, {}); // No grouped tasks, bail. if (Object.keys(groupedTasks).length === 0) { return res.status(200).json({ message: "No tasks to remind" }); } // Build an aggregate data object containing email and the count of tasks assigned to them. const recipientCounts = Object.keys(groupedTasks).map((key) => { return { email: key, count: groupedTasks[key].length }; }); // Iterate over all recipients and send the email. recipientCounts.forEach((recipient) => { const emailData = { from: fromEmails, to: recipient.email }; const taskIds = groupedTasks[recipient.email].map((task) => task.id); // There is only the one email to send to this author. if (recipient.count === 1) { const onlyTask = groupedTasks[recipient.email][0]; emailData.subject = `New Task Reminder - ${onlyTask.title} - ${formatDate(onlyTask.due_date)}`; emailData.html = generateEmailTemplate( generateTemplateArgs(onlyTask.title, onlyTask.created_by, onlyTask.due_date, onlyTask.id) ); } // There are multiple emails to send to this author. else { const allTasks = groupedTasks[recipient.email]; emailData.subject = `New Task Reminder - ${allTasks.length} Tasks require your attention`; emailData.html = generateEmailTemplate({ header: `${allTasks.length} Tasks require your attention`, subHeader: `Please sign in to your account to view the Task details.`, body: `` }); } if (emailData?.subject && emailData?.html) { // Send Email sendMail("remind", emailData.to, emailData.subject, emailData.html, taskIds, (taskIds) => { client.request(UPDATE_TASKS_REMIND_AT_SENT, { taskIds, now: moment().toISOString() }); }); } }); // Sixth step would be to set the remind_at_sent to the current time. res.status(200).json({ status: "success" }); } catch (err) { res.status(500).json({ status: "error", message: `Something went wrong sending Task Reminders: ${err.message || "An error occurred"}` }); } }; module.exports = { taskAssignedEmail, tasksRemindEmail };