Reputation: 1079
Made a cloud function which will send email of invitation and upon successful send it will update a map object field but for some reason it is only working for the last object not all of them
Function Code
exports.sendInvitationEmailForMembers = functions.firestore
.document("/organization-members/{OMid}")
.onWrite(async (snap, context) => {
if (snap.after.data() === undefined) {
return null;
}
let authData = nodeMailer.createTransport({
host: "smtp.gmail.com",
port: 465,
secure: true,
auth: {
user: SENDER_EMAIL,
pass: SENDER_PASSWORD,
},
});
// Exit when the data is deleted.
const id = context.params.OMid;
const afterData = snap.after.data();
const db = admin.firestore();
for (var prop in afterData) {
if (afterData[prop].status === "invited") {
const email = afterData[prop].email;
const name = afterData[prop].fullName;
authData
.sendMail({
from: "[email protected]",
to: `${email}`,
subject: "Organization Team Invitation",
text: `Dear ${name}, you have been invited to join the team of 'organization name', please click on the following link to sign up and join. <br> http://localhost:3000/`,
html: `Dear ${name}, you have been invited to join the team of 'organization name', please click on the following link to sign up and join. <br> http://localhost:3000/`,
})
.then(() => {
db.collection("organization-members")
.doc(id)
.update({ [[prop] + ".status"]: "pending" })
.then(() => {
console.log("Successfully updated");
})
.catch(() => {
console.log("Error occured while updating invitation status");
});
})
.catch((err) => console.log(err.message));
}
}
});
Notice that only last object status was changed but it should've changed the whole document objects statuses
Upvotes: 1
Views: 349
Reputation: 26206
There are a number of small problems with the code as it is. These include:
onWrite
function is self-triggered. Each time you send an email, you currently update the document with the send status for that email - however this means that invites are sent again to those which haven't yet had their status updated. This means that in the worst case scenario, if you had N
recipients - each of those users could get sent your invite email up to N
times.sendMail
fails, it will be reattempted every time the document updates. If one particular email were to fail consistently, you may end up in an infinite loop. This can be mitigated by setting a "failed"
status.console.log
and console.error
as appropriate so they are shown properly in the Cloud Functions logs. Such as logging why a function ended.text
property of the email should not contain HTML like <br>
exports.sendInvitationEmailForMembers = functions.firestore
.document("/organization-members/{OMid}")
.onWrite(async (change, context) => {
// If deleted, cancel
if (!change.after.exists) {
console.log("Document deleted. Cancelling function.");
return null;
}
const OMid = context.params.OMid; // keep parameter names consistent.
const data = change.after.data();
const pendingInvitations = [];
for (const index in data) {
if (data[index].status === "invited") {
// collect needed information
pendingInvitations.push({
email: data.email,
fullName: data.fullName,
index
});
// alternatively: pendingInvitations.push({ ...data[index], index });
}
}
// If no invites needed, cancel
if (pendingInvitations.length == 0) {
console.log("No invitations to be sent. Cancelling function.");
return null;
}
// WARNING: Gmail has a sending limit of 500 emails per day.
// See also: https://support.google.com/mail/answer/81126
// Create transport to send emails
const transport = nodeMailer.createTransport({
host: "smtp.gmail.com",
port: 465,
secure: true,
auth: {
user: SENDER_EMAIL, // Suggestion: Use `functions.config()`, see above
pass: SENDER_PASSWORD,
},
});
// Create an object to store all the status changes
const pendingDocUpdate = {};
// For each invitation, attempt to send the email, and stash the result in `pendingDocUpdate`.
// Wait until all emails have been sent.
await Promise.all(pendingInvitations.map((invitee) => {
return transport
.sendMail({
from: "[email protected]", // shouldn't this be SENDER_EMAIL?
to: `${invitee.email}`,
subject: "Organization Team Invitation",
text: `Dear ${invitee.name}, you have been invited to join the team of 'organization name', please visit this link to sign up and join. http://localhost:3000/`,
html: `Dear ${invitee.name}, you have been invited to join the team of 'organization name', please click on the following link to sign up and join. <br> http://localhost:3000/`,
})
.then(() => {
console.log(`Sent invite to ${invitee.email}.`);
pendingDocUpdate[`${invitee.index}.status`] = "pending";
}, (err) => {
console.error(`Failed to invite ${invitee.email}.`, err);
pendingDocUpdate[`${invitee.index}.status`] = "failed"; // save failure, so that emails are not spammed at the invitee
});
}));
// Update the document with the changes
return admin.firestore()
.doc(`/organization-members/${OMid}`)
.update(pendingUpdates)
.then(() => {
console.log('Applied invitation status changes successfully. Finished.')
}, (err) => {
console.error('Error occured while updating invitation statuses.', err);
});
});
Upvotes: 2