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
Related
I have about 50-60 outbound calls to make once an hour. I have a Serverless function calling a flow API. The flow api calls a Function. And the function looks like this:
const makeCalls = (arr, callbackHandler) => {
const client = context.getTwilioClient();
let itemsProcessed = 0;
arr.forEach(item => {
client.calls.create({
url: 'https://channels.autopilot.twilio.com/v1/XXX/XXX/twilio-voice',
to: item.phone,
from: 'XXX',
}, function(err, result) {
itemsProcessed++;
if (err) { console.error(err); return; }
console.log('New phone call started...', result);
});
});
if(itemsProcessed === arr.length) { callbackHandler(); }
};
Every time I execute the script, the phone calls go out just fine, but I get a runtime application timed out error. How do I fix the issue? What am I missing? (I apologize for the janky async handling).
I figured it out. I put my janky async test in the wrong place. It should have been in the function call that returns the result.
On firebase function I need to get data from Paypal and do 4 things :
1. returns an empty HTTP 200 to them.
2. send the complete message back to PayPal using `HTTPS POST`.
3. get back "VERIFIED" message from Paypal.
4. *** write something to my Firebase database only here.
What I do now works but i am having a problem with (4).
exports.contentServer = functions.https.onRequest((request, response) => {
....
let options = {
method: 'POST',
uri: "https://ipnpb.sandbox.paypal.com/cgi-bin/webscr",
body: verificationBody
};
// ** say 200 to paypal
response.status(200).end();
// ** send POST to paypal back using npm request-promise
return rp(options).then(body => {
if (body === "VERIFIED") {
//*** problem is here!
return admin.firestore().collection('Users').add({request.body}).then(writeResult => {return console.log("Request completed");});
}
return console.log("Request completed");
})
.catch(error => {
return console.log(error);
})
As you can see when I get final VERIFIED from Paypal I try to write to the db with admin.firestore().collection('Users')..
I get a warning on compile :
Avoid nesting promises
for the write line.
How and where should I put this write at that stage of the promise ?
I understand that this HTTPS Cloud Function is called from Paypal.
By doing response.status(200).end(); at the beginning of your HTTP Cloud Function you are terminating it, as explained in the doc:
Important: Make sure that all HTTP functions terminate properly. By
terminating functions correctly, you can avoid excessive charges from
functions that run for too long. Terminate HTTP functions with
res.redirect(), res.send(), or res.end().
This means that in most cases the rest of the code will not be executed at all or the function will be terminated in the middle of the asynchronous work (i.e. the rp() or the add() methods)
You should send the response to the caller only when all the asynchronous work is finished. The following should work:
exports.contentServer = functions.https.onRequest((request, response) => {
let options = {
method: 'POST',
uri: "https://ipnpb.sandbox.paypal.com/cgi-bin/webscr",
body: verificationBody
};
// ** send POST to paypal back using npm request-promise
return rp(options)
.then(body => {
if (body === "VERIFIED") {
//*** problem is here!
return admin.firestore().collection('Users').add({ body: request.body });
} else {
console.log("Body is not verified");
throw new Error("Body is not verified");
}
})
.then(docReference => {
console.log("Request completed");
response.send({ result: 'ok' }); //Or any other object, or empty
})
.catch(error => {
console.log(error);
response.status(500).send(error);
});
});
I would suggest you watch the official Video Series on Cloud Functions from Doug Stevenson (https://firebase.google.com/docs/functions/video-series/) and in particular the first video on Promises titled "Learn JavaScript Promises (Pt.1) with HTTP Triggers in Cloud Functions".
I have created an API endpoint that returns an integer when it is successfully accessed with an HTTP Post request. I want an AWS CloudWatch scheduled process to run an AWS Lambda function every minute to check the API endpoint so make sure the value is not zero. I have set up the scheduled AWS CloudWatch process and the AWS Lambda function, where the runtime is Node.js 10. However, when I look at the AWS CloudWatch group's stream log, the logs seem to be out of order. I suspect this is because of the HTTP Request to the API endpoint is running asynchronously, but I ultimately do not know. The time that it takes to log the value of this HTTP Request to the API end point seems to be much longer than the actual time to process the request.
Here is the Node.js lambda function that is being run on a minutely basis:
exports.handler = async (event) => {
var datetime = new Date();
var request = require("request");
var options = {
method: 'POST',
url: 'https://website.com/api/getDataPoints',
headers:
{
'cache-control': 'no-cache',
'content-type': 'text/plain'
},
body: '{"token" : "yT7g8urUFmEZwQrJNHgQGRDA9zScpNzPM3rb"}'
};
await request(options, function (error, response, body) {
if (error)
{
throw new Error(error);
// Email and SMS message that this is having an error
}
if (body == 0)
{
// Email and SMS message that this is having an error
// Restart EC2 server
}
console.log(datetime.toString() + " - " + body + " Data Points!");
});
};
Here is the AWS CloudWatch log, where it's easier to see the delay in logging the response from the HTTP Request:
Any insight into the cause of this perceived log delay or suggestions on how to achieve similar results in a more efficient way would be greatly appreciated! Thank you!
Here is what is happening: the await is returning immediately because the request function does not return a promise, so the callback is happening after the lambda function exits. The reason why there is such a long delay in seeing the log is that when your lambda function becomes idle (meaning all handler functions have returned, even if there are callbacks waiting) AWS can suspend any executing code until a new invocation of the function. So in your case when the next minute rolls around and the lambda function is invoked again, AWS will un-suspend any executing code and the callback kicks off immediately and you see the log for the previous invocation.
To solve this you want to make sure the handler function doesn't return until all work has been completed (which is what I think you were intending to do with the await). So wrap the request call in a function that returns a promise which is resolved in the callback and use await on the wrapper function.
function doRequest() {
return new Promise((resolve, reject) => {
request(options, function (error, response, body) {
if (error){
throw new Error(error);
// Email and SMS message that this is having an error
}
if (body == 0){
// Email and SMS message that this is having an error
// Restart EC2 server
}
console.log(datetime.toString() + " - " + body + " Data Points!");
resolve();
});
});
}
await doRequest();
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 am using nodemailer (v1.0.4) to send emails in my Node application. Here is the code:
smtpUtil.js
var nodemailer = require("nodemailer");
var config = require("../config").mailgun;
var transporter = nodemailer.createTransport({
service: 'Mailgun',
auth: {
user: config.username,
pass: config.password
},
});
transporter.mailSent = function(mailOptions, callback) {
transporter.sendMail(mailOptions, function(error, response) {
if (error) {
console.log("Error in sending mail", error);
callback(new Error(error));
} else {
console.log("Email sent successfully");
callback();
}
});
}
module.exports = transporter;
I just include this smtpUtil.js in other files and then call transporter.mailSent(mailOpts, callbackFn); to send the email.
My question is: how to add max timeout time & gracefully handle those in the process?
The reason I am asking about above configuration is that recently in code, I invoked transporter.mailSent(mailOpts, callbackFn). But due to some reason (possibly infinite timeout), the callbackFn was never triggered (neither success nor failure).
Latest NodeMailer module though gives 3 timeout options, as follows:
options.connectionTimeout how many milliseconds to wait for the
connection to establish
options.greetingTimeout how many milliseconds to wait for the greeting after connection is established
options.socketTimeout how many milliseconds of inactivity to allow
But I am not sure which one would be relevant here to tackle the callbackFn never getting triggered issue.
I solved this issue by adding a max TTL to process the email sending part.
Since I was using kue, it was rather easy to configure a max TTL.