node.js sending mails with a delay - javascript

I am doing a simple thing but that isn't working.
What I want to do is send mails with a delay of 30 seconds.
Here's the code:
user.forEach(function(data) {
var locals = {
fname: data.Name,
your_name: data.From,
}
template.render(locals, function(err, results) {
if (err) {
return console.error(err)
} else {
transporter.sendMail({
to: data.Email,
subject: "Welcome",
replyTo: data.ReplyTo,
html: results.html,
text: results.text,
}, function(error, info) {
console.log("here");
if (error) {
console.log(error);
} else {
console.log('Message sent: ' + info.response);
};
});
}
});
});
Here user is an array of objects with details like Email,from,Name etc.
Each object in array has details of a particular mail to be sent.
I want to send a mail and wait for 30s and then send the second one..and wait and so on.
I have used setInterval and also npm sleep, but that isn't working. It waits for 30s and then sends all mails at once.

You should replace syncronous forEach with asynchronous implementation.
Option1. Use async.js eachLimit and call callback with delay of 30 seconds
Option2. You can write wrapper for your send email function like:
var emails = ['email1', 'email2' /*...*/];
function sendEmailAndWait(email, callback){
// your stuff
transporter.sendMail(email, function(error, info) {
// handle results
if(!emails.length) return callback();
setTimeout(function () {
sendEmailAndWait(emails.shift(), callback);
}, 30*1000)
})
}
sendEmailAndWait(emails.shift(), function(){ /* allDone */});

setTimeout(function() {
template.render(locals, function(err, results) {
if (err) {
return console.error(err)
} else {
transporter.sendMail({
to: data.Email,
subject: "Welcome",
replyTo: data.ReplyTo,
html: results.html,
text: results.text,
}, function(error, info) {
console.log("here");
if (error) {
console.log(error);
} else {
console.log('Message sent: ' + info.response);
};
});
}
});
}, 3000);

Related

Retry to send mail if not sent

I am using nodemailer to send emails through my node app. Sometimes Email does not work and throws an error until I try twice or thrice. I want my program to try again and again until the mail is successfully sent.
Here's my code:
const mailOptions = {
from: from,
to: client.email,
subject: 'Your Photos are ready',
html: mailTemplate
};
transporter.sendMail(mailOptions, function(error, info){
if (error) {
res.status(500).json({
message: "Mail not sent",
error
});
} else {
res.status(200).json({message: "Mail Sent", response: info.response});
}
});
How can I use the same function inside my if block?
Wrap sendMail in a function that returns a Promise
const promiseWrapper = mailOptions => new Promise((resolve, reject) => {
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
reject(error);
return;
}
resolve(info);
});
then in your route make the handler an async function and loop how many time that you want, then check if info exists if it does send 200 if not send 500
app.post('/sendmail', async (req, res) => {
let info;
let error;
for (let i = 0; i < 3; i++) {
try {
info = await promiseWrapper(mailOptions);
break;
} catch (e) {
error = e;
}
}
info
? res.status(200).json({ message: "Mail Sent", response: info.response })
: res.status(500).json({ message: "Mail not send", error }));
});
You can first separate the retry logic in a different file, so you can use it in various places.
Newer versions of nodemailer support promises for transporter.sendMail
// retry.js
// retries a function, called asynchronously, n amount of times
export async function retry(fn, n) {
for (let i = 0; i < n; i++) {
try {
return await fn()
} catch (err) {
console.log(err)
}
}
}
And pass the transporter function to the retry logic, with the amount of times you want to retry (in this example: 3)
import {retry} from '../utils/retry'
// ...
app.post('/sendmail', async (req, res) => {
try {
retry(
() =>
transporter.sendMail({
// your configuration
from: from,
to: client.email,
subject: 'Your Photos are ready',
html: mailTemplate
}),
3 // max retries
)
} catch (err) {
console.log(err)
// failed max retry times
res.sendStatus(500)
}
res.sendStatus(200)
})
const mailOptions = {
from: from,
to: client.email,
subject: 'Your Photos are ready',
html: mailTemplate
};
var i;
for(i = 0; i <= 1; i++) {
transporter.sendMail(mailOptions, function(error, info){
if (error) {
res.status(500).json({
message: "Mail not sent",
error
});
i = 0;
} else {
i = 2;
res.status(200).json({message: "Mail Sent", response: info.response});
}
});
}
Try the code above will run the function again and again if the error occur and it will exit the loop if no error occur.
Wrap it into a function and call in this way:
const mailOptions = {
from: from,
to: client.email,
subject: 'Your Photos are ready',
html: mailTemplate
};
function sendMail(mailOptions) {
transporter.sendMail(mailOptions, function(error, info){
if (error) {
return sendMail(mailOptions)
} else {
return res.status(200).json({message: "Mail Sent", response: info.response});
}
});
}
return sendMail(mailOptions);

Async.js series and node-mysql query's cant get rows

I am currently trying to run a set of MySQL query's in order using async.js series control flow function. But I keep receiving the following error:
throw err; // Rethrow non-MySQL errors
^
TypeError: Cannot read property 'status' of undefined
I have tested the query's in seperate functions outside the async.series and they are fine and give me back the data, the only reason I can think for the error is due to the async nature it doesn't have the data at that time hence the error E.G when I log the rows I get:
[]
[]
[]
Below is the Async function:
function SQLuserDataAsync() {
connection.getConnection(function (err, connection) {
async.series([
function (callback) {
connection.query('SELECT status FROM users WHERE name= ?;',
[userval],
function (err, rows) {
if (rows[0]['status']) {
console.log("Account Status: " + accountval);
} else {
console.log(err);
}
callback(null, 'one');
});
},
function (callback) {
connection.query('SELECT account_type FROM settings_tbl WHERE id=(SELECT id FROM users WHERE name= ?);',
[userval],
function (err, rows) {
if (rows[0]['account_type']) {
var acctype = rows[0]['account_type'];
console.log("Account Type: " + acctype);
} else {
console.log(err);
}
callback(null, 'two');
});
},
function (callback) {
connection.query('SELECT type FROM settings_tbl WHERE id=(SELECT id FROM users WHERE name= ?);',
[userval],
function (err, rows) {
if (rows[0]['type']) {
var type = rows[0]['type'];
console.log("Type: " + type);
} else {
console.log(err);
}
callback(null, 'three');
});
}
]);
connection.release();
});
}
Any suggestions as the reason for the error or what am doing wrong here?
You've missed the main callback function to the async.series function.
function SQLuserDataAsync() {
connection.getConnection(function (err, connection) {
async.series([
function (callback) {
// YOUR CODE
},
function (callback) {
// YOUR CODE
},
function (callback) {
// YOUR CODE
}
], function(error, results) { // <--- this is the main callback
connection.release();
});
});
}
You should call connection.release() inside the main callback, otherwise, the MySQL connection will be released/terminated before the queries are executed (due to the asynchronous nature the code).
if there is a user with defined in userval name it will work.
But let's simplify our code:
function SQLuserDataAsync(userval) {
connection.getConnection(function (err, connection) {
async.waterfall([
// getting user
function (next) {
connection.query(
'SELECT * FROM users WHERE name = ? LIMIT 1',
[userval],
function (err, result) {
next(err, result[0]); // passing user to next function
});
},
// getting settings of user, maybe user_id (not id) in query below
function (user, next) {
connection.query(
'SELECT * FROM settings_tbl WHERE id = ? LIMIT 1',
[user.id],
function (err, result) {
next(err, user, result[0]);
});
},
// handling both user and settings
function (user, settings, next) {
console.log('User: ', user);
console.log('Settings: ', settings);
connection.release();
}
]);
});
}
SQLuserDataAsync('someone');

Can't Set Headers After they are sent - NodeJS

I have a node js app and one of the routes I keep getting "Can't set headers after they are sent error".
What the route does:
Users in my app have certain access levels so this route goes through the users accessLevel array and finds the appropriate access level for this route. And based on the access level of the user who's calling the route has it performs different actions.
The Code:
app.post('/bios/approve', isLoggedIn, function(req, res) {
for (var i = 0; i < req.user.accessLevel.length; i++) {
if (req.user.accessLevel[i] === "Bio Officer") {
Bio.findOneAndUpdate({userID: req.body.userID, bioForSector: req.body.bioForSector}, {
background: req.body.background,
experience: req.body.experience,
skills: req.body.skills,
bioStatus: req.body.bioStatus
}, function(err, editedBio) {
if (err)
console.log("Error while editing Pending Bio is " + err);
else if (editedBio) {
User.findOneAndUpdate({accessLevel: "Bio Designer"}, {
$push: {biosPending: editedBio._id}
}, function(err, user) {
if (err) {
console.log("The error while finding lineManager is " + err);
} else if (user) {User.findOneAndUpdate({accessLevel: "Bio Officer"}, {
$pull: {
biosPending: editedBio._id
}
}, function(err, bioOfficer) {
if (err) {
console.log("The error while finding lineManager is " + err);
}
res.json("Bio Done!")
});
}
});
}
});
} else if (req.user.accessLevel[i] === "Bio Designer") {
// Currently Empty
} else {
Bio.findOneAndUpdate({userID: req.body.userID,bioForSector: req.body.bioForSector}, {
background: req.body.background,
experience: req.body.experience,
skills: req.body.skills,
bioStatus: req.body.bioStatus
}, function(err, editedBio) {
if (err)
console.log("Error while editing Pending Bio is " + err);
else if (editedBio) {
User.findOneAndUpdate({accessLevel: "Bio Officer"}, {$push: {biosPending: editedBio._id}
}, function(err, user) {
if (err) {
console.log("The error while finding lineManager is " + err);
} else if (user) {
User.findOneAndUpdate({email: editedBio.lineManagerEmail}, {$pull: {biosPending: editedBio._id}
}, function(err, bioOfficer) {
if (err) {
console.log("The error while finding lineManager is " + err);
}
res.json("bio Done!")
});
}
});
}
});
}
}
});
Any help will be greatly appreciated. Does anyone know what am I doing wrong?
Can't Set Headers After they are sent
means you are sending response multiple times for a single request.
From you code what i can suggest is:
for (var i = 0; i < req.user.accessLevel.length; i++) {
if(--req.user.accessLevel.length == 0){
res.json("Bio Done!")
}
}
First try add res.End(); after res.json().
If that doesn't work can you please add the code of 'isLoggedIn'?
Every time you send back a response you should use the return word too. You want to return to make sure no code after the line gets executed and send another response again accidentally.
E.g.: return res.json("bio Done!")

What's the proper use of Bluebird or Q Promises in this situation?

I'm using a node machines package (machinepack-wepay) to communicate with Wepay and I'd like to be able to chain it properly.
Take the following example where we will be registering a user, creating an account and sending the email confirm. Along the way we will be storing some of the result info in mongo.
var WePay = require('machinepack-wepay');
// ... extraneous code removed for brevity
var member = req.session.member;
if( !_.has( member, 'wepay' ) ) {
WePay.userRegister({
clientId: config.wepay_client_id,
clientSecret: config.wepay_client_secret,
email: member.email,
scope: 'manage_accounts,collect_payments,view_user,send_money',
firstName: member.firstName,
lastName: member.lastName,
originalIp: req.headers['x-forwarded-for'],
originalDevice: req.headers['user-agent'],
tosAcceptanceTime: Math.floor(new Date() / 1000),
callbackUri: config.site_url + '/wepay/user?member=' + member.id,
useProduction: isProd
}).exec({
error: function (err) {
yourErrorHandler(err);
},
success: function (result) {
Member.update({id: member.id}, {wepay: result}, function (err, updated) {
if (err) {
yourErrorHandler(err);
}
else {
member = updated[0];
WePay.accountCreate({
accessToken: member.wepay.access_token,
name: 'Account Name',
description: 'My new account'
}).exec({
error: function (err) {
yourErrorHandler(err);
},
success: function (result) {
Member.update({id: member.id}, {wepay_account: result}, function (err, updated) {
if (err) {
sails.log.error("error updating page:", err);
}
req.session.member = updated[0];
// PATTERN CONTINUES HERE
});
}
});
}
});
}
});
}
else{
WePay.userDetails({
accessToken: member.wepay.access_token,
useProduction: false,
}).exec({
error: function (err){
yourErrorHandler(err);
},
success: function (result){
_.extend( member.wepay, result );
Member.update({id: req.session.current_page.id}, member, function (err, updated) {
if (err) {
sails.log.error("error updating page:", err);
}
req.session.member = updated[0];
// PATTERN CONTINUES HERE
});
},
});
}

Nodemailer's sendMail function returns "undefined"

Hi!
I have the following code. The commented lined never get executed and info.response returns "undefined". Could you help me figure out why it's returning "undefined" and why the commented parts don't get executed, please?
Thank you very much.
app.js:
app.get('/send', function (req, res) {
var mailOptions = {
from: req.query.from,
to: 'chul#stackexchange.com',
subject: 'Applicant',
text: req.query.name + req.query.content
};
console.log(mailOptions);
transport.sendMail(mailOptions, function (error, info) {
if (error) {
console.log(error);
res.end("error");
} else {
console.log("Message sent: " + info.repsonse);
res.end("sent"); // This part does NOT get executed.
};
});
});
index.html:
<script type='text/javascript'>
$(document).ready(function() {
var from, name, content;
$("#send_app").click(function() {
from = $("#from").val();
name = $("#name").val();
content = $("#content").val();
$("message").text("Submitting the application ...");
$.get("http://localhost:3000/send", {
from: from,
name: name,
content: content
}, function(data) {
if (data == "sent") { // This block does NOT get executed
console.log("sent received");
$("#message").empty().html("The e-mail has been sent.");
} else {
console.log(data);
}
});
});
});
</script>
From the looks of it, I don't see see the initialization of the transport. Try something like:
var transport = nodemailer.createTransport({
service: 'SendGrid',
auth: {
user: yourUserName,
pass: yourPassword
}
});
// Then the transport you initialized
var mailOptions = {
from: req.query.from,
to: 'chul#stackexchange.com',
subject: 'Applicant',
text: req.query.name + req.query.content
};
console.log(mailOptions);
transport.sendMail(mailOptions, function (error, info) {
if (error) {
console.log(error);
res.end("error");
} else {
console.log("Message sent: " + info.repsonse);
res.end("sent"); // This part does NOT get executed.
};
});

Categories

Resources