I'm setting up a mailer to send an email to the relevant recipient about some details of an appointment request.
I'm having an issue where the IF statement that runs to decide who the necessary recipient should be is running after the email transporter and is giving me an error saying that no recipient has been defined.
Here is the code
let recipientEmail;
if (careHome === 'ACareHome') {
admin.database().ref('managers').once("value").then((snapshot) => {
let managerEmail = snapshot.child("Manager Name").val();
recipientEmail = managerEmail;
console.log(`Recipient is ${recipientEmail}`);
});
}
const mailOptions = {
from: '*****', // sender address
subject: `An appointment has been requested at ${ACareHome}.`,
html: `Hello, an appointment has been booked at ${ACareHome} on ${date} at ${time}. The requestors name is, ${firstName}. You can email them on ${email}.`
};
mailOptions.to = recipientEmail;
transporter.sendMail(mailOptions, function(error, info){
if(error){
return console.log(error);
}
console.log('Message sent: ' + info.response);
})
In the Firebase logs, I am getting these errors
It's showing that the IF statement is being run after the transporter function causing it to error.
I've tried all manor of things but cant seem to get it to play ball!
Help is appreciated, cheers!
Pulling data from your database is async, your current code ignores that and tries to continue before the promise has resolved.
This is quite a common error when people aren't used to promises. Promises are basically pretty callbacks, instead of (err, info) you get a function of .then and .catch (and a few others).
You need to put your email code inside the .then function. So it'll become something like this:
function sendEmail() {
let recipientEmail;
if (careHome === 'ACareHome') {
admin.database().ref('managers').once("value").then((snapshot) => {
let managerEmail = snapshot.child("Manager Name").val();
recipientEmail = managerEmail;
console.log(`Recipient is ${recipientEmail}`);
const mailOptions = {
from: '*****', // sender address
subject: `An appointment has been requested at ${ACareHome}.`,
html: `Hello, an appointment has been booked at ${ACareHome} on ${date} at ${time}. The requestors name is, ${firstName}. You can email them on ${email}.`
};
mailOptions.to = recipientEmail;
return new Promise(function promise(resolve, reject) {
transporter.sendMail(mailOptions, function(error, info){
if (error) {
return console.log(error);
return reject(error);
}
console.log('Message sent: ' + info.response);
return resolve(info);
});
});
});
}
}
I've done three things here.
I've wrapped everything in a function for ease.
I've moved the email sending code into the promise .then function. This means once something is fetched from the database you're sending the email, so you're now waiting for the response.
I've wrapped the transporter.sendMail in a function. There are utilities for doing this but for clarity I've shown you how to do this by hand. This means the new function from point 1 now returns a promise. You can now use the top function sendMail the same way you've done your database code, by calling sendMail().then(result...).catch(error...).
It's often more idiomatic to not mix callback and promise code by wrapping up any callback oriented code with things like Bluebird.promisify, or wrapping up a callback oriented function by hand the way I have. This means you're just dealing with .then and .catch type code.
I apologies if you already knew about Promises vs Callbacks, it's just a common theme I see on questions so I've answered it in full.
Related
I have model where i can store the values and send an email. I need to send an email with attachment but its is not working it is throwing some error.
can anyone help me how to send an email with attachment.
career.js
'use strict';
const app = require('../../server/server');
module.exports = function(Career) {
Career.afterRemote('create', function(context, remoteMethodOutput, next) {
next();
// console.log(context.result)
Career.app.models.Email.send({
to: 'lakshmipriya.l#gmail.com',
from: 'lakshmipriya.l#gmail.com',
subject: 'Career Form',
html: '<em>Hi,</em>',
attachments: [
{ // utf-8 string as an attachment
path: './files/resume/860e032e-a8e6-478a-beeb-6a7225ead701.docx'
}
],
},
function(err, mail) {
// console.log(context.result.email)
console.log('email sent!');
console.log(err);
});
});
Per the stack trace, this line is calling an undefined function: cb(err).
To get the reason for the mail failure you'll want to print the err.
Instead of cb you should call next, there is no cb function defined in your code (probably you took the code from 2 different examples, that's why cb is there).
Secondly, before you will call next(err) I would check if the error exists, otherwise, your code will call the next tick if there is no error.
I have a NodeJs application that runs a variation of tasks but one of them is to produce a report and email it to my client. I am using Mailgun to send the email and have no issues with it sending email elswhere in my code.
As the report needs to be generated into a CSV file which could be lengthy and then attached to the email I have written them into an async series.
The problem is when I put everything together, I get an error message from Mailgun
{ message: '\'from\' parameter is missing' }
My code is very simple as below
async.series([
function(callback) {
User.find({}, function(err, foundUsers){
if(err) {
console.log(err);
} else {
User.csvReadStream(foundUsers)
.pipe(fs.createWriteStream('dailyReport.csv'));
callback(null, 'Created Report');
}
});
},
function(callback) {
sendReport();
callback(null, 'Sent Email');
}
],
function(err, results) {
console.log(results);
});
With the function here
function sendReport () {
var filepath = path.join(__dirname, '../', 'dailyReport.csv');
var data = {
from: "mailgun#myemail.co.uk",
to: 'me#myemail.co.uk',
subject: 'Daily Report',
text: 'Daily CSV report from ...',
attachment: filepath
};
console.log(data);
mg.messages().send(data, function (error, body) {
console.log(body);
});
console.log("Report Sent");
return;
}
I have sent a support message to Mailgun but they have been unable to help so far.
The strange thing is, if I call the function outside of the series, the email is sent. If I call the function inside the series but without the attachment element, it also sends the email!
I am wondering if it's something fundamental with the way the series (I have also tried this with async waterfall) handles the variables.
I'm having an issue sending the same json object to two separate endpoints. I need to send one copy to a database, and the other copy back to the client. I'm getting can't set headers after they are sent. Which I have gathered is an error saying that res.json() is called once, and can not be called a second time because the headers have been "baked in". I'm pretty newb to development, any suggestions or explanation would be very helpful.
Note: the whole code executes successfully, then crashes the server with the error message above.
paypal.payment.execute(paymentId, execute_payment_json, function (error, payment) {
if (error) {
console.log(error.response);
throw error;
} else {
console.log("Get Payment Response");
console.log(JSON.stringify(payment));
const userData = {paymentID : payment.id};
UserData.addUserData(userData, function(err, userData) {
if (err) {
throw err;
}
res.json(userData);
});
res.json(userData)
}
});
})
You are right when you write that you can't call res.json() a second time. You can only send one response per request. res.json() sends a response, so you can only call it once (you have probably seen this question already).
You don't have to send a response to the database. Only the client that sent the request should receive a response. Calling res.json() will not send anything to the database. In order to store the userData object in the database, you have to call the function that does that. In your case, I assume you are doing that with UserData.addUserData(). This is where the storing happens, not by sending a response to it.
The function you send in as an argument to UserData.addUserData() is most likely a callback that is called AFTER storing the userData object is finished. Basically, UserData.addUserData() will do all the stuff it's supposed to do, and then continue with the function you have written after that. In there you can call res.json(). You can remove the last call to res.json(). It's not needed as you will call it in the callback function after storing in the database is finished.
paypal.payment.execute(paymentId, execute_payment_json, function (error, payment) {
if (error) {
console.log(error.response);
throw error;
} else {
console.log("Get Payment Response");
console.log(JSON.stringify(payment));
const userData = {paymentID : payment.id};
UserData.addUserData(userData, function(err, userData) {
if (err) {
throw err;
}
res.json(userData);
});
}
});
})
I have a node app (version 8.11.1 using Typescript 2.8.1) that catches uncaught exceptions using the uncaughtException hook like this:
process.on('uncaughtException', (err) => {
await sendEmail(recipient, subject, body);
});
I'm calling an asynchronous method inside the handler to send out an email and the code isn't working. It appears that the node process dies before the async call can finish and I never receive the email.
It looks like you may not be able to successfully use async methods inside this handler. The documentation doesn't say that outright but it implies it stating
The correct use of 'uncaughtException' is to perform synchronous
cleanup of allocated resources
I'm not trying resume operation or do anything funky. All I want to do is send out and email stating that the system crashed. I am not able to find any libraries that will synchronously send emails so I'm at a bit of a loss on how to handle this.
I've seen one suggestion to synchronously write the data to a file (or database) and have some outside processing polling for the existence of that file and sending the email if it exists. I suppose that would work but it's pretty hacky. Any better solutions?
Update:
Okay well after running some more tests it looks like you can actually run async code from inside the uncaughtException handler just fine. The following works:
const mailer = require('nodemailer');
process.on('uncaughtException', async err => {
const transport = mailer.createTransport({
service: 'gmail',
auth: {
user: 'someone#email.com',
pass: 'abc'
}
});
const mailOptions = {
from: 'someone#email.com',
to: 'someone.else#email.com',
subject: 'hi',
text: 'there'
};
transport.sendMail(mailOptions, (error, info) => {
if (error) {
console.log(error);
}
else {
console.log(info);
}
});
});
throw new Error('boom');
The above code works fine as a standalone app, but if I copy it into my codebase, it doesn't work. The code executes but I don't get an email (presumably the app dies before it can finish sending). So there must be something else going on in my environment that is preventing it from working. I'll keep digging.
I don't know what library you use to send an email and what version of node js you use but if you are using node js version greater than 7 you can use async/await and send an email as below
var mailgun = require('mailgun-js')({apiKey: api_key, domain: domain});
process.on('uncaughtException', async (err) => {
var data = {
from: 'User <me#samples.mailgun.org>',
to: 'serobnic#mail.ru',
subject: 'ERROR MESSAGE',
text: `Caught exception: ${err}\n`,
};
var body = await mailgun.messages().send(data);
console.log(body);
});
// using callback - supported for all versions
process.on('uncaughtException', (err) => {
var data = {
from: 'User <me#samples.mailgun.org>',
to: 'serobnic#mail.ru',
subject: 'ERROR MESSAGE',
text: 'Caught exception:' + err,
};
mailgun.messages().send(data, function (err, body) {
console.log(body);
});
});
I would like to get some help with the following problem. I'm writing my bsc thesis, and this small part of code would be responsible for registering a user. (I'm new at nodejs actually). I'm using express and mongoose for this too.
I would like to process the request data, and check for some errors, first I would like to check if all fields exist, secondly if someone already registered with this e-mail address.
Based on the errors (or on success), I would like to send different responses. If a field is missing, then a 400 Bad request, if a user exists, then 409 Conflict, and 200 OK, if everything is ok. But I would only like to do the callback if there are no errors, but I'm kinda stuck here... I get the error Can't set headers after they are sent, which is obvious actually, because JS continues processing the code even if a response is set.
app.post('/register', function (req, res) {
var user = new User(req.body);
checkErrors(req, res, user, registerUser);
});
var registerUser = function(req, res, user){
user.save(function(err, user){
if (err) return console.log(err);
});
res.sendStatus(200);
};
var checkErrors = function(req, res, user, callback){
var properties = [ 'firstName', 'lastName', 'email', 'password', 'dateOfBirth' ];
for(var i = 0; i < properties.length; i++){
if(!req.body.hasOwnProperty(properties[i])){
res.status(400).send('field ' + properties[i] + ' not found');
}
}
var criteria = {
email: req.body.email
};
User.find(criteria).exec(function(err, user){
if(user.length > 0){
res.status(409).send('user already exists');
}
});
callback(req, res, user);
};
I think the problem is in the for loop in checkErrors. Since you call res.status(400).send() within the loop, you can end up calling it multiple times, which will trigger an error after the first call since a response will already have been sent back to the client.
Inside the loop, you can instead add missing fields to an array, then check the length of the array to see if you should respond with a 400 or continue. That way, you will only call res.status(400).send() one time.
For example:
...
var missingFields = [];
for(var i = 0; i < properties.length; i++){
if(!req.body.hasOwnProperty(properties[i])){
missingFields.push(properties[i]);
}
}
if(missingFields.length > 0) {
return res.status(400).send({"missingFields" : missingFields});
}
...
In general, I advise that you put return in front of each res.send() call, to ensure that no others are accidentally called later on.
An example of this is:
User.find(criteria).exec(function(err, user){
// We put return here in case you later add conditionals that are not
// mutually exclusive, since execution will continue past the
// res.status() call without return
if(user.length > 0){
return res.status(409).send('user already exists');
}
// Note that we also put this function call within the block of the
// User.find() callback, since it should not execute until
// User.find() completes and we can check for existing users.
return callback(req, res, user);
});
You probably noticed that I moved callback(req, res, user). If we leave callback(req, res, user) outside the body of the User.find() callback, it is possible that it will be executed before User.find() is completed. This is one of the gotchas of asynchronous programming with Node.js. Callback functions signal when a task is completed, so execution can be done "out of order" in relation to your source code if you don't wrap operations that you want to be sequential within callbacks.
On a side note, in the function registerUser, if user.save fails then the client will never know, since the function sends a 200 status code for any request. This happens for the same reason I mentioned above: because res.sendStatus(200) is not wrapped inside the user.save callback function, it may run before the save operation has completed. If an error occurs during a save, you should tell the client, probably with a 500 status code. For example:
var registerUser = function(req, res, user){
user.save(function(err, user){
if (err) {
console.error(err);
return res.status(500).send(err);
}
return res.sendStatus(201);
});
};
Your call to registerUser() is defined after the route and would be undefined since it's not a hoisted function.
Your use of scope in the closures isn't correct. For your specific error, it's because you're running res.send() in a loop when it's only supposed to be called once per request (hence already sent headers a.k.a. response already sent). You should be returning from the function directly after calling res.send() as well.