How to send emails that loop through arrays from firestore database - node.js

I'm trying to send a user receipt from an eCommerce store. How do I loop through the data to send
I have tried using [] on the dynamic data.
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
admin.initializeApp();
const db = admin.firestore();
// Sendgrid Config
import * as sgMail from "#sendgrid/mail";
const API_KEY = functions.config().sendgrid.key;
const TEMPLATE_ID = functions.config().sendgrid.template;
sgMail.setApiKey(API_KEY);
//FUNCTIONS
export const newOrder = functions.firestore
.document("checkout/{checkoutId}/products/{productId}")
.onCreate(async (change, context) => {
// Read booking document
const postSnap = await db
.collection("checkout/{checkoutId}/products")
.doc(context.params.productId)
.get();
const booking = postSnap.data() || {};
//Email
const msg = {
to: "wilmutsami#gmail.com",
from: "test#example.com",
templateId: TEMPLATE_ID,
dynamic_template_data: {
subject: "Hey there, thank you for your order!",
name: booking.name,
amount: booking.amount
}
};
//Send it
return sgMail.send(msg);
});
Expected results are an email to the user displays a table of items that you ordered

If you want to get the data of the document that triggered the Cloud Function at checkout/{checkoutId}/products/{productId} you don't need to do
await db
.collection("checkout/{checkoutId}/products")
.doc(context.params.productId)
.get();
As explained in the doc:
When a function is triggered, it provides a snapshot of the data
related to the event. You can use this snapshot to read from or write
to the document that triggered the event, or use the Firebase Admin
SDK to access other parts of your database.
You can easily get the values of the document fields through the snap DocumentSnapshot as follows:
export const newOrder = functions.firestore
.document("checkout/{checkoutId}/products/{productId}")
.onCreate(async (snap, context) => {
const docData = snap.data();
const name = docData.name;
const amount = docData.amount;
// You can then use those values in the rest of your code
const msg = {
to: "wilmutsami#gmail.com",
from: "test#example.com",
templateId: TEMPLATE_ID,
dynamic_template_data: {
subject: "Hey there, thank you for your order!",
name: name,
amount: amount
}
};
return sgMail.send(msg);
});

Related

I have the create and saving of the user's email, but I can't do an email update. Use firebase, firestore and react

This is my code:
exports.saveUserEmail = functions.region('europe-central2').auth.user().onCreate((user) => {
const email = user.email;
const uid = user.uid;
const dt = dateTime.create();
const formatted = dt.format("Y-m-d H:M:S");
return admin.firestore().collection('users').doc(uid).set({uid: uid, email: email, created_at: formatted});
});
and i tried do update like this:
exports.saveEditedEmail = functions.region('europe-central2').auth.user().updateUser((user, uid) => {
const email = user.email;
return admin.firestore().collection('users').doc(uid).set({uid: uid, email: email,});
})
Where is my mistake?
There isn't any onUpdate() auth trigger for Cloud functions. Instead your can create a callable function and call it directly from client side to update your user.
exports.addMessage = functions.https.onCall((data, context) => {
const { email } = data;
// update email in Firebase Auth and Firestore
});
Alternatively, you can directly update the document in Firestore if the user tries to update their own profile. You can setup the following security rules so a user can update their own profile only:
match /users/{userId} {
allow update: if request.auth.uid == userId;
}

Cloud functions for Firebase FCM notifications to multiple users

I am using nodeJS with firebase for my flutter/firebase mobile app
I would like to send notifications to all users that have a certain query met. ie all users who have radiology as their specialty. So that they will be notified when a new article is added to the database
However I am unsure why my code (below) doesn't work to get notification tokens for all users with this query.
My database structure is users/notificationTokens/Ids of all tokens for that user stored in field 'token'
exports.sendToDevice5 = functions.firestore
.document('Articles/{paper}')
.onCreate(async (snapshot, context) => {
const paper = context.params.paper;
const item = snapshot.data();
if (item.subspecialty == "RadMSK" || item.subspecialty == "RadMS") {
const tokens = await admin.firestore().collection('users').where("specialty", "==", "RADIOLOGY").get().then(
snapshot.forEach((doc) => {
const docs = admin.firestore().collection('users').doc(doc.id).collection('notificationTokens').get();
return docs.data().token;
}));
const payload = {
notification: {
title: `${item.title}!`,
body: `New Journal`,
sound: "default",
},
data: {click_action: 'FLUTTER_NOTIFICATION_CLICK'},
};
return admin.messaging().sendToDevice(tokens, payload);
}
});

My onCreate funciton in Functions of firebase is not creating my desired collection in the cloud database

I just typed a code in my index.js file of functions (firebase CLI).According to my code there must be a timeline collection created in cloud database of firebase.Function is healthy and there are no errors it gets deployed and even in the logs everything works fine. But still timeline collection is not created in the cloud databaese when I follow a user in my app.
this is my code:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
exports.onCreateFollower = functions.firestore
.document("/followers/{userId}/userFollowers/{followerId}")
.onCreate(async (snapshot, context) => {
console.log("Follower Created", snapshot.id);
const userId = context.params.userId;
const followerId = context.params.followerId;
// 1) Create followed users posts ref
const followedUserPostsRef = admin
.firestore()
.collection("posts")
.doc(userId)
.collection("userPosts");
// 2) Create following user's timeline ref
const timelinePostsRef = admin
.firestore()
.collection("timeline")
.doc(followerId)
.collection("timelinePosts");
// 3) Get followed users posts
const querySnapshot = await followedUserPostsRef.get();
// 4) Add each user post to following user's timeline
querySnapshot.forEach(doc => {
if (doc.exists) {
const postId = doc.id;
const postData = doc.data();
return timelinePostsRef.doc(postId).set(postData);
}
});
});
Since you want to execute a variable number of asynchronous calls in parallel, you should use Promise.all(), in order to wait that all these different asynchronous calls are completed before indicating to the CF platform that it can cleanup the CF. See https://firebase.google.com/docs/functions/terminate-functions for more details.
exports.onCreateFollower = functions.firestore
.document("/followers/{userId}/userFollowers/{followerId}")
.onCreate(async (snapshot, context) => {
const userId = context.params.userId;
const followerId = context.params.followerId;
// ...
// 3) Get followed users posts
const querySnapshot = await followedUserPostsRef.get();
// 4) Add each user post to following user's timeline
const promises = [];
querySnapshot.forEach(doc => {
//query results contain only existing documents, the exists property will always be true and data() will never return 'undefined'.
const postId = doc.id;
const postData = doc.data();
promises.push(timelinePostsRef.doc(postId).set(postData));
});
return Promise.all(promises);
});

how can i add the sendgrid webhook event Json response in a firebase cloud firestore using node.js

I have no idea how to implement this thing but before that, I have done a part of SendGrid where any document is created then it will send the email to the user. but this part what I am asking I has no idea how to proceed.this is my first part of this implementation wherein any collection if a new record is created then it will send email to the particular email and there is a response called event Object I want to write a cloud function to store the data. and I don't know how to start this function or proceed with this problem.
"use strict";
const functions = require("firebase-functions");
const admin = require("firebase-admin");
var serviceAccount1 = require("./key.json");
const newProject = admin.initializeApp({
credential: admin.credential.cert(serviceAccount1),
databaseURL: "xyz"
});
const sgMail = require("#sendgrid/mail");
const sgMailKey = "key";
sgMail.setApiKey(sgMailKey);
exports.sentMail = functions.firestore
.document("/Offices/{officeId}")
.onCreate((documentSnapshot,event) => {
const documentData = documentSnapshot.data()
const officeID = event.params.officeId;
console.log(JSON.stringify(event))
const db = newProject.firestore();
return db.collection("Offices").doc(officeID).get()
.then(doc => {
const data = doc.data();
const msg = {
to: "amarjeetkumars34#gmail.com",
from: "singhamarjeet045#gmail.com",
text: "hello from this side",
templateId: "d-8ecfa59aa9d2434eb8b7d47d58b4f2cf",
substitutionWrappers: ["{{", "}}"],
substitutions: {
name: data.name
}
};
return sgMail.send(msg);
})
.then(() => console.log("payment mail sent success"))
.catch(err => console.log(err));
});
and the expected output of my question be like a collection name XYZ wherein an object there are three fields like
{email:"xyz#gmail.com",
event:"processed",
timestamp:123555558855},
{email:"xyz#gmail.com",
event:"recieved",
timestamp:123555558855},
{email:"xyz#gmail.com",
event:"open",
timestamp:123555558855}
As you will read in the Sendgrid documentation:
SendGrid's Event Webhook will notify a URL of your choice via HTTP
POST with information about events that occur as SendGrid processes
your email
To implement the HTTP endpoint in your Firebase Project, you will implement an HTTPS Cloud Function that will be called by the Sendgrid webhook through an HTTPS POST request.
Each call from the Sendgrid webhook will concern a specific event and you will be able, in your Cloud Function, to get the value of the event (processed, delivered, etc...).
Now, you need in your Cloud Function to be able to link a specific event with a specific email that was previously sent through your Cloud Function. For that you should use custom arguments.
More precisely, you would add to your msg object (that you pass to the send() method) a unique identifier. A classical value is a Firestore document ID, like event.params.officeId but could be any other unique ID that you generate in you Cloud Function.
Example of implementation
In your Cloud Function that sends the email, pass the officeId in a custom_args object, as shown below:
exports.sentMail = functions.firestore
.document("/Offices/{officeId}")
.onCreate((documentSnapshot,event) => {
const documentData = documentSnapshot.data();
const officeId = event.params.officeId;
const msg = {
to: "amarjeetkumars34#gmail.com",
from: "singhamarjeet045#gmail.com",
text: "hello from this side",
templateId: "d-8ecfa59aa9d2434eb8b7d47d58b4f2cf",
substitutionWrappers: ["{{", "}}"],
substitutions: {
name: documentData.name
},
custom_args: {
"officeId": officeId
}
};
return sgMail.send(msg)
.then(() => {
console.log("payment mail sent success"));
return null;
})
.catch(err => {
console.log(err)
return null;
});
});
Note that you get the data of the newly created document (the one which triggers the Cloud Function) through documentSnapshot.data(): you don't need to query for the same document in your Cloud Function.
Then, create a simple HTTPS Cloud Function, as follows:
exports.sendgridWebhook = functions.https.onRequest((req, res) => {
const body = req.body; //body is an array of JavaScript objects
const promises = [];
body.forEach(elem => {
const event = elem.event;
const eventTimestamp = elem.timestamp;
const officeId = elem.officeId;
const updateObj = {};
updateObj[event] = true;
updateObj[event + 'Timestamp'] = eventTimestamp;
promises.push(admin.firestore().collection('Offices').doc(officeId).update(updateObj));
});
return Promise.all(promises)
.then(() => {
return res.status(200).end();
})
})
Deploy it and grab its URL as shown in the terminal: it should be like https://us-central1-<your-project-id>.cloudfunctions.net/sendgridWebhook.
Note that here I use admin.firestore().collection('Offices').... You may use const db = newProject.firestore(); ... db.collection('Offices')...
Also note that the body of the HTTPS POST request sent by the Sendgrid webhook contains an array of JavaScript objects, therefore we will use Promise.all() to treat these different objects, i.e. write to the Firestore document with officeId the different events.
Then you need to set-up the Webhook in the Sendgrid platform, in the "Mail Settings/Event Notification" section, as explained in the doc and as shown below.

Promise not returning value on request

I have been trying to get this to work, but am new to NodeJS. I suspect the issue is due to async, but am not familiar with how it works.
The idea behind this code is that it monitors a firebase database change and sends an email to the users. I am getting everything from the change snapshot, and using the values to check another table for user data. The request is not returning before the email gets sent and I am unsure why.
Edit I should specify that the email function sgMail is firing off before I get the results from the requests. I've tried putting a delay, but I am still not getting the result to return in time.
Here's my index.js
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require('firebase-functions');
var requestify = require('requestify');
//SendGrid
const SENDGRID_API_KEY = functions.config().sendgrid.key;
const sgMail = require('#sendgrid/mail');
sgMail.setApiKey(SENDGRID_API_KEY);
// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.packingListEmail = functions.database.ref('Order/{orderID}')
.onUpdate(event => {
// Grab the current value of what was written to the Realtime Database.
const eventSnapshot = event.data;
//Here You can get value through key
var shipperInfo = eventSnapshot.child("fk_shipper_id").val();
var travelerInfo = eventSnapshot.child("fk_traveler_id").val();
//Print value of string
console.log(shipperInfo);
//Get Shipper Info
const shipperPath = 'https://shlep-me-f516e.firebaseio.com/User/'+shipperInfo+'.json';
requestify.get(shipperPath)
.then(function(response) {
// Get the response body (JSON parsed or jQuery object for XMLs)
shipperResult = response.getBody();
console.log(shipperResult.email);
return shipperResult;
});
function getTravelerData() {
return new Promise(resolve => {
requestify.get('https://shlep-me-f516e.firebaseio.com/User/' + travelerInfo + '.json')
.then(function (response) {
resolve(response.getBody())
});
});
}
var TravelD = getTravelerData();
//Send an email
const msg = {
to: 'andrew#shlepme.com',
from: 'support#shlepme.com',
subject: 'New Follower',
// text: `Hey ${toName}. You have a new follower!!! `,
// html: `<strong>Hey ${toName}. You have a new follower!!!</strong>`,
// custom templates
templateId: 'd1ccfeb9-2e2d-4979-a3ca-c53975fe486e',
substitutionWrappers: ['%', '%'],
substitutions: {
'%shipper_name%': "Test",
'traveler_name': TravelD.name
// and other custom properties here
}
};
console.log('Sending email');
console.log(TravelD);
return sgMail.send(msg)
});
Any ideas? I have been trying to figure this out.
It seems that you need to understand about Promises first.
When you start using promises you will need to ALWAYS use them and chain one with the other.
So I would rewrite your code like this: (not tested)
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require("firebase-functions");
var requestify = require("requestify");
//SendGrid
const SENDGRID_API_KEY = functions.config().sendgrid.key;
const sgMail = require("#sendgrid/mail");
sgMail.setApiKey(SENDGRID_API_KEY);
// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require("firebase-admin");
admin.initializeApp(functions.config().firebase);
exports.packingListEmail = functions.database
.ref("Order/{orderID}")
.onUpdate(event => {
// Grab the current value of what was written to the Realtime Database.
const eventSnapshot = event.data;
//Here You can get value through key
var shipperInfo = eventSnapshot.child("fk_shipper_id").val();
var travelerInfo = eventSnapshot.child("fk_traveler_id").val();
//Print value of string
console.log(shipperInfo);
//Get Shipper Info
const shipperPath = "https://shlep-me-f516e.firebaseio.com/User/" + shipperInfo + ".json";
requestify.get(shipperPath)
.then(function(response) {
// Get the response body (JSON parsed or jQuery object for XMLs)
var shipperResult = response.getBody();
console.log(shipperResult.email);
return shipperResult;
})
.then(function (shipperResult) {
//Send an email
const msg = {
to: "andrew#shlepme.com",
from: "support#shlepme.com",
subject: "New Follower",
// text: `Hey ${toName}. You have a new follower!!! `,
// html: `<strong>Hey ${toName}. You have a new follower!!!</strong>`,
// custom templates
templateId: "d1ccfeb9-2e2d-4979-a3ca-c53975fe486e",
substitutionWrappers: ["%", "%"],
substitutions: {
"%shipper_name%": "Test",
traveler_name: shipperResult.name
// and other custom properties here
}
};
console.log("Sending email");
console.log(shipperResult);
return sgMail.send(msg);
});
});

Resources