223
server/email/tasksEmails.js
Normal file
223
server/email/tasksEmails.js
Normal file
@@ -0,0 +1,223 @@
|
||||
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 <noreply@imex.online>`,
|
||||
rome: `Rome Online <noreply@romeonline.io>`,
|
||||
promanager: `ProManager <noreply@promanager.web-est.com>`
|
||||
});
|
||||
|
||||
/**
|
||||
* 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: `<a href="${fromEmails}/manage/tasks/alltasks?taskid=${taskId}">Please sign in to your account to view the Task details.</a>`
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 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: `<ul>
|
||||
${allTasks
|
||||
.map(
|
||||
(task) =>
|
||||
`<li><a href="${fromEmails}/manage/tasks/alltasks?taskid=${task.id}">${task.title} - ${formatDate(task.due_date)}</a></li>`
|
||||
)
|
||||
.join("")}
|
||||
</ul>`
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
Reference in New Issue
Block a user