Firebase doesn't send emails after deploying + no logs - node.js

I implemented the code below and it works perfectly fine when I run it in on local machine with npm start
However, when I deploy it on Firebase, the sendOrderEmail function doesn't work.
I don't even get any logs.
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const {email, password} = require('./config');
const nodemailer = require('nodemailer');
const htmlToText = require('nodemailer-html-to-text').htmlToText;
admin.initializeApp();
const mailTransport = nodemailer.createTransport({
service: 'gmail',
auth: {
user: email,
pass: password
}
});
mailTransport.use("compile", htmlToText());
const APP_NAME = 'the.Travelest';
exports.sendUserEmail = functions.database
.ref("/lettersRequest/{pushId}")
.onCreate(request => {
return sendOrderEmail(request.val().email);
});
async function sendOrderEmail(email){
const mailOptions = {
from: `${APP_NAME}`,
to: email,
subject: `Your order from ${APP_NAME}`,
html: `
<div>
<strong> Hello </strong>
World
</div>
`
};
return await mailTransport.sendMail(mailOptions);
}
UPDATE
After checking the logic, I believe that I've found the issues in another place.
So, the logic is - when I click on SUBMIT in form, I send the data to Firebase Runtime Database. And this action triggers the function above and send the email.
However, the SUBMIT button doesn't trigger the process it should trigger. I don't see even console.log result in the console, when I click on SUBMIT
Again, it works locally, when I run nom start
Here is the submit handler:
const handleSubmit = event => {
event.preventDefault();
setErrors(validate(values))
setIsSubmitting(true)
try{
const newRequestRef = database.ref('lettersRequest').push();
const newRequest = {
country: values.country,
travelersAmount: values.travelersAmount,
introExtro: values.introExtro,
comments: values.comments,
email: values.email,
phone: values.phone
};
newRequestRef.set({
email: values.email,
request: newRequest
});
console.log("no error")
}catch(e){
console.log("error")
}
}

You are not handling the promise returned by set(). Try this:
const handleSubmit = event => {
event.preventDefault();
setErrors(validate(values))
setIsSubmitting(true)
const newRequestRef = database.ref('lettersRequest').push();
const newRequest = {
country: values.country,
travelersAmount: values.travelersAmount,
introExtro: values.introExtro,
comments: values.comments,
email: values.email,
phone: values.phone
};
newRequestRef.set({
email: values.email,
request: newRequest
}).then(() => {
console.log("Request Added in Database")
}).catch(e => console.log(e));
}
If it logs 'Request Added in Database' then it has been added successfully and your function should trigger.
Additionally, this can also be done by a Callable Function which means you call the function directly from the client, do all the validation, add the data in database and send the email instead of using database triggers.

So, the issue was even more trivial than I could expect :D
I did deploy to firebase, but I've never did commit and build project...
As a result, nothing was updated after each deploy.
Stupid mistake

Related

Firebase SendEmail function works only on 3rd click

I am using firebase SendEmail function in my React app (for Contact Form). After all deployment, when I test it through my Contact Form, it doesn't work upon "Submit" request. But it works only when I click immediate after getting second failer message after timeouts. If I repeat new Submit later, it again doesn't work but only with 3rd click.
Here is the error message:
Access to fetch at 'https://us-central1-myapp.cloudfunctions.net/sendEmail' from origin 'https://www.mydomain.co' 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.
And then "Failed to load resource: net::ERR_FAILED".
When it works, no error message there of course.
Here is my code (C:...\root\functions\index.js):
//import needed modules
const functions = require('firebase-functions');
const nodemailer = require('nodemailer');
//when this cloud function is already deployed, change the origin to 'https://your-deployed-app-url
const cors = require('cors')({ origin: 'https://www.mydomain.co' });
//create and config transporter
let transporter = nodemailer.createTransport({
host: 'mail.mydomain.co',
port: 465,
secure: true, // true for 465, false for other ports
auth: {
user: 'info#mydomain.co',
pass: '********'
},
});
//export the cloud function called `sendEmail`
exports.sendEmail = functions.https.onRequest((req, res) => {
//for testing purposes
console.log(
'from sendEmail function. The request object is:',
JSON.stringify(req.body)
);
//enable CORS using the `cors` express middleware.
cors(req, res, () => {
//get contact form data from the req and then assigned it to variables
const name = req.body.data.name;
const email = req.body.data.email;
const company = req.body.data.company;
const subject = req.body.data.subject;
const message = req.body.data.message;
//config the email message
const mailOptions = {
from: email,
to: `info#mydomain.com`,
subject: subject,
text: `Name: ${name}, Company: ${company} Message: ${message}`,
};
//call the built in `sendMail` function and return different responses upon success and
failure
return transporter.sendMail(mailOptions, (error, info) => {
if (error) {
return res.status(500).send({
data: {
status: 500,
message: error.toString(),
},
});
}
return res.status(200).send({
data: {
status: 200,
message: 'sent',
},
});
});
});
});
Also, although Postman sends emails from localhost app, it absolutely doesn't send email when the app deployed (when used the function's online url: https://us-central1-myapp.cloudfunctions.net/sendEmail).
Any ideas about this issue, please?

Nodemailer with E-Mail Templating working locally but not in AWS Lambda environment

I am developing a small webapp that has user accounts. So I want to send E-Mails regarding their registration and E-Mail Address Confirmation.
I am using the Serverless Framework with Express and Node.js as well as Nodemailer with email-templates.
here is the mailer.js function to send a confirmation email:
function sendConfirmation (name, address) {
const transporter = nodemailer.createTransport({
host: 'smtp.strato.de',
port: 465,
secure: true,
auth: {
user: process.env.STRATO_USER,
pass: process.env.STRATO_PASSWORD,
}
});
const email = new Email({
transport: transporter,
send: true,
preview: false,
});
console.log("Sending Email");
email.send({
template: 'confirmation',
message: {
from: 'company',
to: address,
},
locals: {
fname: name,
}
}).then(() => console.log('email has been sent!'));
}
module.exports = {
sendRegister,
sendConfirmation
};
And here is the code of the route. I look for the user in the database and update him to be confirmed. Then I try to send the email.
router.post("/confirmation", async (req, res) => {
userId = req.body.userId;
let userUpdated = false;
let updatedUser;
updatedUser = await User.findByIdAndUpdate({_id: userId}, {"confirmed": true}, function (err, result) {
if(err){
res.send(err)
}
else{
userUpdated = true;
}
});
if (userUpdated) {
await sendConfirmation(updatedUser.name, updatedUser.email);
res.json({error: null, data: "User confirmed"});
}
});
If I test this with app.listen on localhost I receive the E-Mail without any problems. As soon as I deploy it to AWS with Serverless, the user gets updated in the DB, but the E-Mail is not send. It seems that Lambda does not wait until the promise of the email.send() is there? I also tried to explicitly allow the Lambda function to communicate with port 465 on outbound traffic. No luck.
In the logs of the Lambda function no error is shown, but also no confirmation of the sent email.
Has anyone an idea what I am missing? Thank you very much!
EDIT 1:
I just found out, that if I use await sleep(2000) with an according function
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
right after calling the sendConfirmation function and it works without problems. It seems, that the sendConfirmation function will not wait for the promise of email.send. Any idea how to solve this? I am fairly new to promises.
Thanks in advance!

Email Database Results

I have a firebase database where onCreate Google Cloud Functions calls nodemailer and sends me an email. It all works fine but now I am trying to also include the data that was added to the database in the email. I can't get it to work, seemingly because it is not in a text format and I've tried converting to text and that doesn't seem to do it. What am I doing wrong?
const functions = require('firebase-functions');
const nodemailer = require('nodemailer');
// Configure the email transport using the default SMTP transport and a GMail account.
// For other types of transports such as Sendgrid see https://nodemailer.com/transports/
// TODO: Configure the `gmail.email` and `gmail.password` Google Cloud environment variables.
const gmailEmail = functions.config().gmail.email;
const gmailPassword = functions.config().gmail.password;
const mailTransport = nodemailer.createTransport({
service: 'gmail',
auth: {
user: gmailEmail,
pass: gmailPassword,
},
});
exports.sendWelcomeEmail = functions.database.ref('/PickupRequests/{pushId}')
.onCreate(async(snapshot, context) => {
const val = snapshot.data;
const mailOptions = {
from: '<noreply#firebase.com>',
to: "mike#puravidalaundry.com",
subject: "New Pickup Request",
text: val //How do i convert this to a text format?
};
try {
await mailTransport.sendMail(mailOptions);
console.log('email sent');
} catch(error) {
console.error('There was an error while sending the email:', error);
}
return null;
});
To get the value from the snapshot, use snapshot.val(). So:
const val = snapshot.val();
Note that this is shown in an example in the documentation on handling event data for Realtime Database triggered Cloud Functions.
figured it out. I had to create a new variable const newvar = JSON.stringify(val) and then i said text: newvar

If MFA enabled in AWS cognito, do I need to create js on client side to call cognitoUser.authenticateUser() because of the promt for code?

I am using reactjs and node for server side.
As you can see in the "mfa required" part of the code below, if this is all on node, then I can't really do "prompt" the user for the code, I have to pass this back to the front end.
Tried solution: If I do pass the MFA required to front end and get the users input then send it back to node to call "respondToAuth" I am getting two MFA codes in my SMS message.
Have I tried other solutions?
I am hesitant to use amplify because everything is on the front end, I would ideally like to do my authentication on the back end (thus node).
Another option I am leaning towards is just using initiateAuth api instead of "cognitoUser.AuthenticateUser". This way I can get the challenge response and pass it on in sequence. But as per my initial question, I am wondering if I can implement the below code and be able to route users to input MFA code (without duplicating MFA sms message)
AWS.config.update({
region: process.env.Region
});
var AmazonCognitoIdentity = require('amazon-cognito-identity-js');
const poolData = { //--Moved to env variables
UserPoolId: process.env.UserPoolId, // your user pool id here
ClientId: process.env.ClientId // your app client id here
};
const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
router.post('/api/authenticateuser', (req, res) => {
const val = req.body;
var userData = {
Username: val.value.user, // your username here
Pool: userPool
};
var authenticationData = {
Username: val.value.user, // your username here
Password: val.value.pass, // your password here
};
const authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(authenticationData);
const cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function(result) {
console.log('You are now logged in.');
console.log(result);
const accessToken = result.getAccessToken().getJwtToken();
const idToken = result.getIdToken().getJwtToken();
res.json({
accessToken,
idToken
});
},
onFailure: function(err) {
res.json(err);
},
mfaRequired: function(codeDeliveryDetails) {
// console.log("mfa enabled");
// var verificationCode = prompt('Please input verification code' ,'');
// cognitoUser.sendMFACode(verificationCode, this);
// res.json({ MFA:codeDeliveryDetails})
}
});
})

How to send customize e-mail with Firebase cloud functions

I want to send a mail once a user is created with a firebase cloud functions, using nodemail and postmark.
I followed this tutorial : Tutorial link from Dave Martin
But keep getting this error:
There was an error while sending the welcome email: { status: 422, message: 'Zero recipients specified', code: 300 }
Here is my code to send a mail from cloud functions:
//Mail
const nodemailer = require('nodemailer')
const postmarkTransport = require('nodemailer-postmark-transport')
// Google Cloud environment variable used:
// firebase functions:config:set postmark.key="API-KEY-HERE"
const postmarkKey = functions.config().postmark.key
const mailTransport = nodemailer.createTransport(postmarkTransport({
auth: {
apiKey: postmarkKey
}
}))
exports.OnUserCreation = functions.auth.user().onCreate((user) =>
{
console.log("user created: " + user.data.uid);
console.log("user email: " + user.data.email);
sendEmail(user);
})
function sendEmail(user)
{
// Send welcome email to new users
const mailOptions =
{
from: '"test" <test#test.com>',
to: user.email,
subject: 'Welcome!',
html: 'hello'
}
// Process the sending of this email via nodemailer
return mailTransport.sendMail(mailOptions)
.then(() => console.log('Welcome confirmation email sent'))
.catch((error) => console.error('There was an error while sending the welcome email:', error))
}
My postmark.key is already setup in the firebase config... The API tell me the problem is the format I use to send the mail informations.. How could I fix it ?
Update
I also tried to modify the mailOptions as follow and still the same error:
const mailOptions = {
from: 'test#test.com',
to: user.email,
subject: 'Welcome!',
textBody: 'hello'
}
Decided to restart from scratch by following only postmark documentation (wich is really good by the way).
So here are the very simple steps to send mail from events in firebase cloud functions:
1- download packages:
Run: npm install postmark
2- register to postmark
Register to PostMark
- then find your API key.
3- setup firebase environment config:
Run : firebase functions:config:set postmark.key="API-KEY-HERE"
4 index.js code to be added:
//Mail
const postmark = require('postmark')
const postmarkKey = functions.config().postmark.key;
const mailerClient = new postmark.ServerClient(postmarkKey);
exports.OnUserCreation = functions.auth.user().onCreate((user) => {
console.log("user created: " + user.data.uid);
console.log("user email: " + user.data.email);
return sendEmail(user);
})
// Send welcome email to new users
function sendEmail(user) {
const mailOptions = {
"From": "XYZ#YOURDOMAIN.com",
"To": user.data.email,
"Subject": "Test",
"TextBody": "Hello from Postmark!"
}
return mailerClient.sendEmail(mailOptions)
.then(() => console.log('Welcome confirmation email sent'))
.catch((error) => console.error('There was an error while sending the welcome email:', error))
}
That's it.
No need to download nodemailer nor use a transporter.

Resources