Async code inside node uncaughtException handler - node.js

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);
});
});

Related

Twilio programmable SMS not sending in deployed Lambda function

I am working on a Serverless AWS service that uses Twilio's programmable SMS to deliver text messages.
My setup is consistently delivering messages successfully when I run the stack locally (e.g. sls offline start), but in the deployed environment I seem to be unable to even call the method on the Twilio client.
Here's how the message delivery is set up:
const twilio = require('twilio');
const twilioClient = twilio(
process.env.TWILIO_SID,
process.env.TWILIO_TOKEN,
{
lazyLoading: true,
}
);
export function sendMessage(user, message) {
twilioClient.messages.create({
from: process.env.TWILIO_NUMBER,
to: user.phone,
body: message,
}, function(err, message) {
console.log('error', err);
console.log('message', message);
});
}
// And then usage in a Serverless Function Handler
function example(event, context, callback) {
context.callbackWaitsForEmptyEventLoop = false;
// user is also determined here
sendMessage(user, 'This is a message');
return {
body: JSON.stringify({}),
statusCode: 200
};
}
Locally, running this works and I am able to see the output of the message log, with nothing on the error log. However, when deployed, running this yields nothing -- the method appears to not even get called (and I can verify in the Twilio logs that no API call was made), therefor no error or message logs are made in the callback.
In debugging I've tried the following:
I've logged all the environment variables (Twilio SSID, auth token, phone number), as well as the function arguments, and they all appear to be in place. I also checked out the Lambda function itself to make sure the environment variables exist.
I've examined my CloudWatch logs; no errors or exceptions are logged. Other than the Twilio method not getting called the Lambda function is executed without issue.
I've tried logging things like twilio and twilioClient.messages.create to make sure the client and function definition didn't get wiped out somehow.
I thought maybe it had to do with context.callbackWaitsForEmptyEventLoop so I changing it from false to true.
I have come up empty, I can't figure out why this would be working locally, but not when deployed.
Edit: per the Twilio client example, if you omit the callback function the method will return a Promise. I went ahead and tried to await the response of the method:
export function sendMessage(user, message) {
return twilioClient.messages.create({
from: process.env.TWILIO_NUMBER!,
to: user.phone,
body: message,
});
}
// Usage...
async function example(event, context, callback) {
context.callbackWaitsForEmptyEventLoop = false;
try {
const message = await sendMessage(user, 'This is a message');
console.log('message', message)
} catch (error) {
console.log('error', error);
}
return {
body: JSON.stringify({}),
statusCode: 200
};
}
In this example the Lambda function is successful, but neither the message nor the error are logged.
I tried this and it works. I tried to make my code as similar to use, with a few changes.
const twilio = require('twilio');
const twilioClient = twilio(
process.env.TWILIO_SID,
process.env.TWILIO_TOKEN
);
let user = '+14075551212';
function sendMessage(user, message) {
return twilioClient.messages.create({
from: process.env.TWILIO_NUMBER,
to: user,
body: message,
});
}
exports.handler = async function(event, context, callback) {
try {
const message = await sendMessage(user, 'This is a message');
console.log('message', message);
callback(null, {result: 'success'});
} catch (error) {
console.log('error', error);
callback("error");
}
};

NodeJS mailparser not being executed

I am new to NodeJS. The following code snippet from my script is not executing nor it is logging any errors.
console.log('Process attachment');
const simpleParser = require('mailparser').simpleParser;
console.log('Process attachment');
simpleParser(data.Body, (err, mail) => {
if (err) {
console.log('attachment error');
console.log(err)
callback(null, null);
} else {
console.log('attachment success');
console.log(mail)
console.log(mail.attachments[0])
console.log(mail.attachments[0].content)
console.log(mail.attachments[0].content.toString('ascii'))
callback(null, null);
}
})
console.log('Exit');
process.exit();
Process attachment and Exit are being logged in the console but for some reason the code never goes in either the if or the else. So it looks like the simpleParser function is not being executed for some reason. data.Body contains a full email body. Is there anything obvious i am missing ? Thanks.
Why don't you use promises instead ?
This will work
simpleParser(data.Body).then(mail=>{
console.log('attachment success');
console.log(mail)
console.log(mail.attachments[0])
console.log(mail.attachments[0].content)
console.log(mail.attachments[0].content.toString('ascii'))
}).then(()=>{
console.log('Exit');
process.exit();
}).catch(err=>{
console.log('attachment error');
console.log(err);
})
And if you want to make it look simpler, cleaner use Async/Await like this
const parseMail = async ()=>{
try {
let mail = await simpleParser(data.Body);
console.log('attachment success');
console.log(mail)
console.log(mail.attachments[0])
console.log(mail.attachments[0].content)
console.log(mail.attachments[0].content.toString('ascii'))
}
catch(err) {
console.log('attachment error');
console.log(err);
}
console.log('Exit');
process.exit();
}
You are terminating the script prematurely.
simpleParser is being executed asynchronously. Therefore this bit console.log('Exit'); process.exit(); is being called before your simpleParser has finished.
We experienced this exact same behavior. The above responders are correct in that the console.log commands are not logging anything within the mailparser function because it is running asynchronously and the calling function is exiting without waiting on the mailparser to do its thing.
The simple solution for us was to just call the mailparser with await so the calling function waits on mailparser to complete before it continues.
So, instead of:
simpleParser(data.Body, (err, mail) => {console.log('message');});
Try this:
let mail = await simpleParser(data.Body);
if (mail != null) {console.log('message');};
For what it's worth, I think the asynchronous simpleParser function without the await should still be running through its internal code. It's just the logging messages won't be recorded as the calling function may have exited at the time.

Getting Mailgun error when sending email with attachment as part of async series

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.

Sending an eMail with Lambda, NodeJs and Nodemailer doesn't work

I'm trying to send an email from Lambda with a previously SES authenticated account; with node.js using Nodemailer. There is no error, but it doesn't send neither.
This is the process I'm using for:
module.exports.eviarCorreoPrueba = (event, context, callback) => {
context.callbackWaitsForEmptyEventLoop = false;
console.log('inicia envio de correos');
var transporter = nodemailer.createTransport({
//pool: true,
host: 'email-smtp.us-east-1.amazonaws.com',
port: 465,
secure: true,
auth: {
user: 'user',
pass: 'pass'
}
});
console.log('se crea transporte ');
var mailOptions = {
from: 'test#email.com',
to: 'test#email.com',
subject: 'Prueba Lambda',
html: 'hello World'
};
console.log('se asignan las opciones de correo');
console.log('inicia envio de correo');
transporter.sendMail(mailOptions, function (error, info) {
if (error) {
callback(null, {
statusCode: 200,
body: JSON.stringify({
input: 'not send'
})
})
} else {
console.log('Email sent');
}
});
console.log('funcion finalizada');
};
And these are the log answer results:
Just in case anyone gets stuck with 'nodemailer not working in Lambda' issue:
The code in your post works in local server because:
In your local environment, there is a running server to schedule all call stacks including synchronous and asynchronous tasks. Once you called transporter.sendMail() in local environment, it will be placed to the end of the current call stack and will be executed until the last execution in your call stack finished. In your case, the asynchronous function transporter.sendMail() will be scheduled to be called after console.log('funcion finalizada');
Why it is not working in Lambda:
Every time when you call the lambda function, it will execute the code and then kill the process after the execution finished, which means it won't be able to call the asynchronous transporter.sendMail() because after console.log('funcion finalizada'); the lambda function process will be terminated and the scheduled asynchronous tasks will be cleared before get executed.
To make it work in Lambda:
1) Change your function to an async function
module.exports.eviarCorreoPrueba = async (event, context) => { ... }
2) await your transporter.sendMail() to be called before continue
const response = await new Promise((rsv, rjt) => {
transporter.sendMail(mailOptions, function (error, info) {
if (error) {
return rjt(error)
}
rsv('Email sent');
});
});
return {
statusCode: 200,
body: JSON.stringify({
input: response
})
}
3) Last thing to make your nodemailer emailbot work:
You need to turn Less secure app access on and Allow access to your Google Account because you are simply using username and password to connect your Gmail account.
*Note: If you want to use securer method to connect your Gmail (eg. OAuth 2.0), you can refer to my article: Create a Free Serverless Contact Form Using Gmail, Nodemailer, and AWS Lambda
Hope this helps anyone who gets stuck with this issue.
Cheers,
Your Lambda is timing out. See the last message in your Cloudwatch logs. You set your lambda timeout to 6 seconds I suppose which is not enough time for the lambda to send out the request (via transporter.sendMail) and get the response. Try increasing the lambda time. To about 30 seconds maybe?
The way I set timeouts for new lambdas I design is to run them several times till I get an average completion time. Then I add 20 seconds to that time.
Also, transporter.sendMail is asynchronous function. This means console.log('funcion finalizada') will probably run before your function completes (yet your function hasn't actually completed).
Read more on asynchronous javascript and callbacks: https://medium.com/codebuddies/getting-to-know-asynchronous-javascript-callbacks-promises-and-async-await-17e0673281ee
Also, If you want to write asynchronous code in a synchronous looking way use async/await

If statement running after function being called

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.

Resources