Handle timeout in NodeMailer - node.js

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.

Related

Node JS runs on local dev machine but not on active server

My Problem:
My goal is to send weekly mails and for a test I made with the modules node-schedule and nodemailer a website which should send me every minute an email.
My issue:
when I'm running it on my local machine (Windows OS) on the localhost everything works, but as long as I'm uploading it on the server I'm getting:
[enter image description here][1]
[1]: https://i.stack.imgur.com/Ett2l.png
On english:
The website is not reachable
g.ch has refused connection.
Try the following:
Check connection
Check proxy and firewall
ERR_CONNECTION_REFUSED
The server has the same node js version as the client. The Server is a linux Debian server version 10.4 and the computer I'm programing on has the windows 10 OS.
I've tried to add the following thing to my server.js:
const nodemailer = require('nodemailer');
const schedule = require('node-schedule');
var transporter = nodemailer.createTransport({
host: "chestonag-ch.mail.protection.outlook.com", // hostname
secure: false, // use SSL
port: 25, // port for secure SMTP
tls: {
rejectUnauthorized: false
}
});
var mailOptions = {
from: 'no-reply-mailer#company.ch', // sender address
to: 'name#company.ch', // list of receivers
cc: '', // Comma separated list or an array
subject: 'test upgrde nodemailer subject', // Subject line
html: '<b>Hello world </b>' // html body
};
const job = schedule.scheduleJob('30 * * * * *', function() {
transporter.sendMail(mailOptions, function(error, info) {
if (error) {
console.log("/sendmail error");
console.log(error);
res.sendStatus(500);
return;
} else {
console.log("Message sent: " + info.response);
socketTimeout: 30 * 1000 // 0.5 min: Time of inactivity until the connection is closed
transporter.close(); // shut down the connection pool, no more messages
res.sendStatus(200);
}
transporter.close(); // shut down the connection pool, no more messages
});
});
and the same thing to one of my .js files in the routes.
That you know: The project is with vue.
I have a client side and a server side and my changes were only on the server side.
When I run it local it worked and on the server it didn't and the logs were also empty.
if anyone has an idea how to fix that or an alternative solution, please let me know.
If it's good to know: my goal at the end is to make an emailing system, where you can subscribe to channels and unsubscribe them to receive weekly mails.

Able to connect to redis but set/get times out

I'm trying to do a get() from my AWS Lambda (NodeJS) on ElastiCache Redis using node_redis client. I believe that I'm able to connect to redis but I'm getting Time out (Lambda 60 sec time out) when I'm trying to perform a get() operation.
I have also granted my AWS lambda Administrator access just to be certain that it's not a permissions issue. I'm hitting lambda by going to AWS console and clicking the Test button.
Here is my redisClient.js:
const util = require('util');
const redis = require('redis');
console.info('Start to connect to Redis Server');
const client = redis.createClient({
host: process.env.ElastiCacheEndpoint,
port: process.env.ElastiCachePort
});
client.get = util.promisify(client.get);
client.set = util.promisify(client.set);
client.on('ready',function() {
console.log(" subs Redis is ready"); //Can see this output in logs
});
client.on('connect',function(){
console.log('subs connected to redis'); //Can see this output in logs
})
exports.set = async function(key, value) {
console.log("called set!");
return await client.set(key, value);
}
exports.get = async function(key) {
console.log("called get!"); //Can see this output in logs
return await client.get(key);
}
Here's my index.js which calls the redisClient.js:
const redisclient = require("./redisClient");
exports.handler = async (event) => {
const params = event.params
const operation = event.operation;
try {
console.log("Checking RedisCache by calling client get") // Can see this output in logs
const cachedVal = await redisclient.get('mykey');
console.log("Checked RedisCache by calling client get") // This doesn't show up in logs.
console.log(cachedVal);
if (cachedVal) {
return {
statusCode: 200,
body: JSON.stringify(cachedVal)
}
} else {
const setCache = await redisclient.set('myKey','myVal');
console.log(setCache);
console.log("*******")
let response = await makeCERequest(operation, params, event.account);
console.log("CE Request returned");
return response;
}
}
catch (err) {
return {
statusCode: 500,
body: err,
};
}
}
This is the output (time out error message) that I get:
{
"errorMessage": "2020-07-05T19:04:28.695Z 9951942c-f54a-4b18-9cc2-119eed65e9f1 Task timed out after 60.06 seconds"
}
I have tried using Bluebird (changing get to getAsync()) per this: https://github.com/UtkarshYeolekar/promisify-redis-client/blob/master/redis.js but still got the same behavior.
I also changed the port to use a random value (like 8088) that I'm using to create client (to see the behavior of connect event for a failed connection) - in this case I still see a Timed Out error response but I don't see the subs Redis is ready and subs connected to redis in my logs.
Can anyone please point me in the right direction? I don't seem to understand why I'm able to connect to redis but the get() request times out.
I figured out the issue and posting here in case it helps anyone in future as the behavior wasn't very intuitive for me.
I had enabled AuthToken param while setting up my redis. I was passing the param to lambda with the environment variables but wasn't using it while sending the get()/set() requests. When I disabled the AuthToken requirement from redis configuration - Lambda was able to hit redis with get/set requests. More details on AuthToken can be found here: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticache-replicationgroup.html#cfn-elasticache-replicationgroup-authtoken

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

Async code inside node uncaughtException handler

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

NodeJS xmpp server

I make the first steps in the node js and xmpp
I need to run at xmpp server on node js for messaging
Here's the process:
I use node-xmpp server https://github.com/astro/node-xmpp
run the example of a server (/examples/c2s.js)
join to server with two clients (clients tested on other servers jabber - it works and the messages are sending through)
Clients have authorization on my server.
But when I send a message from one client to another, the message comes to the server (I see it in the logs)
and that was the message does not come to recipient
I don `t know where to look for the problem
server configuration ? routing ? messaging may be necessary to add yourself ?
help me plz
my server code (by examples)
var xmpp = require('../lib/node-xmpp');
var c2s = new xmpp.C2SServer({
port: 5222,
domain: 'localhost'
});
// On Connect event. When a client connects.
c2s.on("connect", function(client) {
c2s.on("register", function(opts, cb) {
console.log("REGISTER");
cb(true);
});
client.on("authenticate", function(opts, cb) {
console.log("AUTH" + opts.jid + " -> " +opts.password);
cb(null);
});
client.on("online", function() {
console.log("ONLINE");
client.send(new xmpp.Message({ type: 'chat' }).c('body').t("Hello there, little client."));
});
client.on("stanza", function(stanza) {
console.log("STANZA" + stanza);
});
client.on("disconnect", function(client) {
console.log("DISCONNECT");
});
});
I run a server and connect to it by this code
var xmpp = require('../lib/node-xmpp');
var argv = process.argv;
if (argv.length < 6) {
console.error('Usage: node send_message.js <my-jid> <my-password> <my-text> <jid1> [jid2] ... [jidN]');
process.exit(1);
}
var cl = new xmpp.Client({ jid: argv[2], password: argv[3] });
cl.addListener('online',
function() {argv.slice(5).forEach(
function(to) {cl.send(new xmpp.Element('message', { to: to,type: 'chat'}).c('body').t(argv[4]));
});
// nodejs has nothing left to do and will exit
// cl.end();
});
cl.addListener('stanza',
function(stanza) {
if (stanza.is('message') &&
// Important: never reply to errors!
stanza.attrs.type !== 'error') {
console.log("New message");
// Swap addresses...
stanza.attrs.to = stanza.attrs.from;
delete stanza.attrs.from;
// and send back.
cl.send(stanza);
}
});
cl.addListener('error',
function(e) {
console.error(e);
process.exit(1);
});
The short answer: change cb(null) to cb(null, opts).
The long answer:
client.on("authenticate", function(opts, cb) {...}) registers what the server will do when the client tries to authenticate itself. Inside node-xmpp, it will look for the authentication mechanism first and the mechanism will then call the callback and retrieve the authentication results via cb.
By default, the Plain authentication is used. You can check out how it works here: https://github.com/node-xmpp/node-xmpp-server/blob/master/lib/authentication/plain.js. With Plain the opts stores the jid and password.
Then to inform node-xmpp that authentication failed/sucessed, we need to look into mechanism, https://github.com/node-xmpp/node-xmpp-server/blob/master/lib/authentication/mechanism.js, inherited by Plain.
this.authenticate(this.extractSasl(auth), function (err, user) {
if (!err && user) {
self.success(user)
} else {
self.failure(err)
}
})
Here, cb requires two parameters. When err is null and user is non-null, it indicates authentication successes. Otherwise, failed.
I am no expert on neither node.js nor xmpp. But reading your code. I assume the "stanza" is the event where a client sent a message. You asked it to log the message, but you gave no instructions on how to route it to the recipient. You should break down the received message on the server into message body and recipient, and ask your server to send it to the recipient.
Alex you have used C2SServer which connects a stream between a server and a client. When you send a message from one client to another they get to server. Now its responsibility of the sever to relay them back to actual receiver.
One possible solution is to keep client object is a global object corresponding to their jids when client is authenticated, when you get a message for that client you can get that from global variable and route the message to actual client kept in global variable.
You can get the text message & receiver JID from server. Just break the stanza in following ways and put this before error listeners:-
cl.on('stanza', function(stanza) {
if (stanza.is('message') && (stanza.attrs.type !== 'error')) {
var body = stanza.getChild('body');
if (!body) {
return;
}
console.log(stanza.attrs.from+" Says: "+body.children[0]);
}
});
In "authenticate", may an argument not be enough for a callback?
NG:
cb(null);
OK:
cb(null, opts);

Resources