Unable to sendEmail using firebase cloud function - javascript

Unable to sendEmail after firestore document creation. I am trying to send a notification email to webapp admin's email once the document on firestore is created. However facing following issues.
index.js
const functions = require('firebase-functions');
const admin = require("firebase-admin")
const nodemailer = require('nodemailer');
admin.initializeApp()
//google account credentials used to send email
var transporter = nodemailer.createTransport({
host: 'smtp.gmail.com',
port: 465,
secure: true,
auth: {
user: '*****#gmail.com',
pass: '******'
}
});
exports.sendEmail = functions.firestore
.document('stories/{sId}')
.onCreate((snap, context) => {
const mailOptions = {
from: `*******#gmail.com`,
to: snap.data().email,
subject: 'contact form message',
html: `<h1>Order Confirmation</h1>
<p>
<b>Email: </b>${snap.data().email}<br>
</p>`
};
return transporter.sendMail(mailOptions, (error, data) => {
if (error) {
return res.send(error.toString());
}
var data = JSON.stringify(data)
return res.send(`Sent! ${data}`);
});
});
Firebase Functions logs
*sendEmail
Billing account not configured. External network is not accessible and quotas are severely limited. Configure billing account to remove these restrictions
sendEmail
Function returned undefined, expected Promise or value*
I am assuming it is safe to ignore the Billing message in logs as it is not mandatory to have a billing plan ?
Any help would be much appreciated.

The error message is telling you that your code returned something other than a promise or value as required. The problem is the way you're using transporter.sendMail(). According to the nodemailer documentation, sendMail will only return a promise if you don't pass a callback method (which you are doing) otherwise it returns undefined. So your function is returning undefined.
What you should do instead remove the callback parameter and instead handle the results from the returned promise. You can also return the promise from the function.
return transporter.sendMail(mailOptions)
.then(data => {
// decide what you want to do on success
})
.catch(err => {
// decide what you want to do on failure
});

Related

Interacting with firebase firestore from firebase cloud functions

I am trying to interact with firebase firestore from my cloud function. The cloud functions looks like the following:
const admin = require("firebase-admin");
const functions = require("firebase-functions");
admin.initializeApp();
const db = admin.firestore();
exports.addVote = functions.https.onCall((data, context) => {
return db
.doc("sdd-enheter/enhet/votes/voteID")
.set({ user: "user", vote: 0 });
});
When calling the function from the client side I get a firebase internal error, indicating that the function has been called but throws an error. What should I do to fix this?
Your function needs to return a promise or otherwise terminate by throwing an https error. Throwing the https error will give the client back a relevant error that it can handle, so consider making that a habit. The function below covers both of those bases. If you still get an error then share that error with us as it appears in the console's function log.
exports.addVote = functions.https.onCall((_data, _context) => {
const db = admin.firestore();
try {
return db.doc("sdd-enheter/enhet/votes/voteID").set({user: "user", vote: 0});
} catch (error) {
throw new functions.https.HttpsError("unknown", "Failed to add vote.", error);
}
});

How could I increase consume timeout in rpc endpoint at rabbitmq?

I use RPC endpoints and in one of them I have the following problem: I do not receive a message so the callback function is not executed on channel.consume().
At that endpoint, I send a message, a process that takes time is running on the server side and it responds to me with a message about whether the process was executed correctly. At other endpoints where the message is sent immediately by the server there is no problem.
I think there is a problem with the timeout. I tried to place the object {timeout: 3600000} after amqpOptions but again the problem was not solved. Specifically, the connection and channel objects have the same parameters regardless of the object I added. How could I change the timeout correctly?
const amqp = require('amqplib/callback_api');
const amqpOptions = {
protocol: 'amqp',
hostname: process.env.RABBITMQ_HOST,
port: process.env.RABBITMQ_PORT,
username: process.env.RABBITMQ_USER,
password: process.env.RABBITMQ_PASS,
vhost: '/',
};
const message = Buffer.from(JSON.stringify({}));
amqp.connect(amqpOptions, (error0, connection) => {
if (error0) { throw error0; }
connection.createChannel((error1, channel) => {
if (error1) { throw error1; }
const correlationId = generateUuid();
channel.consume(replyQueue, (msg) => {
if (JSON.parse(msg.content).error) {
console.log(JSON.parse(msg.content));
const error = JSON.parse(msg.content.toString());
return next(error);
}
console.log(JSON.parse(msg.content));
console.log('msg:',msg);
const {tunnel_info} = JSON.parse(msg.content.toString());
}, {noAck: true});
channel.sendToQueue(`${brokerUri}`,
message, {correlationId, contentType: 'application/json', contentEncoding: 'utf8', replyTo: replyQueue});
});
});
Because channel is unidirectional. You should use two different channels for Publish and Consume.
AMQP specification says:
Channels are unidirectional, and thus at each connection endpoint the incoming and outgoing channels are completely distinct.

Firebase functions integrating with Sendgrid

I'm fairly new with Firebase functions and I'm trying to create a simple onCreate() trigger however I cant seem to get it up and running.
Am I not returning the promise correctly with Sendgrid? Not sure what I am missing
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const sendGrid = require("#sendgrid/mail");
admin.initializeApp();
const database = admin.database();
const API_KEY = '';
const TEMPLATE_ID = '';
sendGrid.setApiKey(API_KEY);
const actionCodeSettings = {
...
};
exports.sendEmailVerify = functions.auth.user().onCreate((user) => {
admin
.auth()
.generateEmailVerificationLink(user.email, actionCodeSettings)
.then((url) => {
const msg = {
to: user.email,
template_id: TEMPLATE_ID,
dynamic_template_data: {
subject: "test email",
name: name,
link: url,
},
};
return sendGrid.send(msg);
})
.catch((error) => {
console.log(error);
});
});
Logs from firebase functions
sendEmailVerify
Function execution started
sendEmailVerify
Function returned undefined, expected Promise or value
sendEmailVerify
Function execution took 548 ms, finished with status: 'ok'
sendEmailVerify
{ Error: Forbidden
sendEmailVerify
at axios.then.catch.error (node_modules/#sendgrid/client/src/classes/client.js:133:29)
sendEmailVerify
at process._tickCallback (internal/process/next_tick.js:68:7)
sendEmailVerify
code: 403,
sendEmailVerify
message: 'Forbidden',
You are not correctly returning the Promises chain in your Cloud Function. You should do as follows:
exports.sendEmailVerify = functions.auth.user().onCreate((user) => {
return admin // <- See return here
.auth()
.generateEmailVerificationLink(user.email, actionCodeSettings)
.then((url) => {
const msg = {
to: user.email,
template_id: TEMPLATE_ID,
dynamic_template_data: {
subject: "test email",
name: name,
link: url,
},
};
return sendGrid.send(msg);
})
.catch((error) => {
console.log(error);
return null;
});
});
There are at least two programming problems here.
You're not returning a promise from the function that resolves when all the async work is complete. This is a requirement. Calling then and `catch is not sufficient. You actually have a return a promise from the function handler.
You're calling sendGrid.send(email), but you never defined a variable email anywhere in the code. If this is the case, then you're passing an undefined value to sendgrid.
There is also the possibility that your project is not on a payment plan, in which case, the call to sendgrid will always fail due to lack of outbound networking on the free plan. You will need to be on a payment plan for this to work at all.

Any data passed to a Firebase Function returns Undefined

I've made a firebase function which every time I pass data to it and try to use the data, it returns that the data is undefined. This is the function I made:
const functions = require('firebase-functions');
// The Firebase Admin SDK to access Cloud Firestore.
const admin = require('firebase-admin');
// CORS Express middleware to enable CORS Requests.
const cors = require('cors')({origin: true});
admin.initializeApp();
exports.addUser = functions.https.onRequest((req, res) => {
const handleError = (error) => {
console.log('Error creating new user:', error);
//sends back that we've been unable to add the user with error
return res.status(500).json({
error: err,
});
}
try {
return cors(req, res, async () => {
console.log(req);
const uid = req.uid;
const dob = req.dob;
const postcode = req.postcode;
const sex = req.sex;
const username = req.username;
admin.firestore().collection('users').doc(uid).set({
dob:dob,
postcode:postcode,
sex:sex,
username:username,
})
.then(function(userRecord) {
console.log('Successfully created new user:', userRecord.username);
// Send back a message that we've succesfully added a user
return res.status(201).json({
message: 'User stored',
id: req.body.uid,
});
})
.catch(function(error) {
return handleError(error);
});
});
} catch (error) {
return handleError(error);
}
});
This is how I call it within react:
const addUserFunc = firebase.functions().httpsCallable('addUser');
console.log("Calling user func " + user.uid)
addUserFunc({
uid:user.uid,
dob:dob,
postcode:postcode,
sex:sex,
username:username,
}).then(function(result) {
console.log(result);
}).catch(err => {
console.log(err)
setErrors(prev => ([...prev, err.message]))
});
I've printed the data before sending the request and it definitely exists. I've also tried getting it within the function using req.body and req.query but this just returns the same.
This is the error I get in the firebase function logs:
Error: Value for argument "document path" is not a valid resource path. The path must be a non-empty string.
at Object.validateResourcePath (/srv/node_modules/#google-cloud/firestore/build/src/path.js:406:15)
at CollectionReference.doc (/srv/node_modules/#google-cloud/firestore/build/src/reference.js:1982:20)
at cors (/srv/index.js:44:51)
at cors (/srv/node_modules/cors/lib/index.js:188:7)
at /srv/node_modules/cors/lib/index.js:224:17
at originCallback (/srv/node_modules/cors/lib/index.js:214:15)
at /srv/node_modules/cors/lib/index.js:219:13
at optionsCallback (/srv/node_modules/cors/lib/index.js:199:9)
at corsMiddleware (/srv/node_modules/cors/lib/index.js:204:7)
at exports.addUser.functions.https.onRequest (/srv/index.js:31:16)
This is the error return in the web console for the react app:
Access to fetch at 'https://***/addUser' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
I tested the function within using the emulator and passing the values using the link which works there but just not when deployed.
Any help would be great.
Your Cloud Function is defined as a HTTPS Function, which means that you can access it over a URL, but then you're calling it from your code as a Callable Function. The two types are different and not interchangeable.
If you want to use the firebase.functions().httpsCallable('addUser'); in your client code, you'll have to modify your Cloud Function to be a Callable Function too. This mostly means that you get the parameters from data instead of res, and return responses instead of sending them through res.
exports.addUser = functions.https.onCall((data, context) => {
...
const uid = context.auth.uid; // automatically passed to Callable Functions
return admin.firestore().collection('users').doc(uid).set({
dob: data.dob,
postcode: data.postcode,
sex: data.sex,
username: data.username,
})
.then(function(userRecord) {
return {
message: 'User stored',
id: req.body.uid,
};
}).catch(err => {
throw new functions.https.HttpsError('dabase-error', error);
})
});
Alternatively, you can leave your Cloud Function as is and instead modify the calling code to use something like fetch().

Extract specific node value via Firebase Cloud functions

Ok so I'm going to start with some background (skip to MY ISSUE for tl;dr):
I have an application in development that passes data from a Google Sheet into a Firebase Realtime Database in the form of a 2d Array. The data layout of the Google sheet is as shown below:
This data is passed into a Firebase Realtime Database under the node masterSheet via an Apps Script function result shown below:
Which is used as the live database for my mobile web application I am developing using the Ionic Framework (preview below):
I have functions which deal with the setting of "Y" and "N" flags at the correct positions for the sub tasks of each job and a function which sets the overall job completion status flag to "Y" when all sub tasks are done working as intended.
I am trying to add in an automatic email service via Firebase Cloud Functions that sends off a "job completion notification" whenever a job's overall "Completed" status is set to "Y" (i.e the value at ref: 'masterSheet/0/1' is equal to "Y").
So far I have managed to get it to successfully send off the emails via a Firebase Cloud Function using nodemailer and the Firebase Admin SDK to all registered users of the Firebase app whenever a job's overall completed status is changed from an "N" to a "Y" via the onUpdate() method and the .ref() of the location to listen at.
Below is my Index.js file containing the cloud function I am using:
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require('firebase-functions');
// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
admin.initializeApp();
// The mail service used
const nodemailer = require('nodemailer');
// Cloud Fucntion to export:
exports.onMessageUpdate = functions.database.ref('/masterSheet/{subArray}/1')
.onUpdate((change) => {
var changeRef = change.after.ref.path;
console.log('changeRef: ' + changeRef);
var newVal = change.after.val();
if (newVal == "Y"){
getUsers();
}
})
// Fucntion to get all registers users of the Firebase Project
function getUsers(){
var userEmails = [];
admin.auth().listUsers()
.then(function(listUsersResult) {
listUsersResult.users.forEach(function(userRecord) {
console.log(userRecord);
userEmails.push(userRecord.email);
sendCompletionEmail(userRecord.email)
});
})
.catch(function(error) {
console.log("Error listing users:", error);
});
}
// Function to send automatic emails
function sendCompletionEmail(email){
var transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
type: 'OAuth2',
user: 'xxxxxxxx#gmail.com',
clientId: 'xxxxxxxx.apps.googleusercontent.com',
clientSecret: 'xxxxxxxxxxxxxxx',
refreshToken: 'xxxxxxxxxxxxxx'
}
})
// Email details:
var mailOptions = {
from: 'xxxxxxx',
to: email,
subject: 'Job completion notification',
text: 'This is an automated message to inform you that a job has been fully completed ' +
'with all of its tasks marked as done. \n\nYou can view this (along with any others) from the Completed ' +
'Jobs page within the app.'
}
transporter.sendMail(mailOptions, function (err, res) {
if(err){
console.log('Error');
} else {
console.log('Email Sent');
}
})
}
MY ISSUE:
I want to be able to include the job title in this automatic email that is sent.
logging the result of change.after.ref.path used in the snippet below:
// Cloud Fucntion to export:
exports.onMessageUpdate = functions.database.ref('/masterSheet/{subArray}/1')
.onUpdate((change) => {
var changeRef = change.after.ref.path;
console.log('changeRef: ' + changeRef);
var newVal = change.after.val();
if (newVal == "Y"){
getUsers();
}
})
Produces this log output:
which contains exactly what I want within it... But I don't know how to get it out...
How can I retrieve the second value from the changeRef variable so that I can pass this onto the sendCompletionEmail() function and use it to refer to the item at position [0] for that node?
something like:
var subArray = changeRef[1]
to get the value: 0 out of masterSheet/0/1
which i can store as a variable and use to refer to the job title of the job that has just been completed in the sent off email.
Thanks for any help!
If you're looking for the 0 from the request, that is available from the second parameter that is passed into your Cloud Function (but that you're not declaring).
exports.onMessageUpdate = functions.database.ref('/masterSheet/{subArray}/1')
.onUpdate((change, context) => {
console.log(context.params.subArray);
})
See the Firebase documentation on handling event data and the reference docs for onUpdate.

Categories