Why is this code sending duplicate emails? - node.js

We're running a scheduler on Azure that runs every minute and checks in a database to see what emails need to be sent for our app and sends them.
This works the vast majority of the time but once in a while, for a few hours, the scheduler starts sending most emails in duplicates (up to 5 copies).
query = "select id, email, textbody, htmlbody, subject from emailTable where sent = 0 AND DateSent IS NULL";
mssql.query(query, {
success: function(results) {
for(var i = 0 ; i < results.length; i++)
{
handleItem(results[i]);
}
},
error: function(err) {
console.log("Error : " + err);
}
});
function handleItem(item) {
sendMail(item);
var query = "update emailTable SET sent = 1, DateSent = GETDATE() where id = ?";
mssql.query(query, [item.id]);
}
function sendMail(objMail)
{
sendgrid.send({
to: objMail.email,
from: 'source#sourcemail.com',
fromname: "Source",
subject: objMail.subject,
text: objMail.textbody,
html: objMail.htmlbody
}, function(err, json) {
if (err) { return console.error(err); }
});
}
When it starts failing it's as if handleItem or sendMail is called multiple times for the same item. Is there anything wrong with the loop or the general logic that would explain this behavior ?

Try writing the update query in the callback function for sendMail(item)
eg. sendMail(item,function(){
var query = "update emailTable SET sent = 1, DateSent = GETDATE() where id = ?";
mssql.query(query, [item.id]);
});
function sendMail(objMail,cb)
{
sendgrid.send({
to: objMail.email,
from: 'source#sourcemail.com',
fromname: "Source",
subject: objMail.subject,
text: objMail.textbody,
html: objMail.htmlbody
}, function(err, json) {
if (err) { cb(err) }
cb(null,json)
});
}

Related

SendGrid Multiple Emails to Multiple Recipients Node JS from Firebase Firestore

I am using SendGrid and Firebase Functions to send multiple emails to multiple recipients. The code that I am using works correctly when sending to a test list of 4 email addresses, but does not work when trying to send to 4,000 email addresses. There is also no error message from SendGrid.
This code also works and returns the list of email addresses printed in the console if the SendGrid block of code is commented out.
Do you know what could be going wrong?
Thank you
exports.adminSendGroupMessage = functions.region('europe-west2').https.onCall((data, context) => {
const emailHTMLData = emailHTMLData;
var emailDataArray = [];
//Fetch contacts list
let testContactsRef = db.collection('contacts-list');
return testContactsRef.get().then(snapshot => {
snapshot.forEach(doc => {
// console.log(doc.id, '=>', doc.data());
console.log("Fetched contact with ID: " + doc.id);
//Extract contact data
const firstName = doc.data().name || "";
const surname = doc.data().surname || "";
const emailAddress = doc.data().emailAddress;
var emailData = {
to: emailAddress,
from: 'fromEmail#email.com',
subject: messageSubject,
text: 'Email for ' + firstName,
html: emailHTMLData,
customArgs: {
ref: 'msg-ref'
},
}
//Add new email data to the array
emailDataArray.push(emailData);
});
return Promise.all(emailDataArray).then(results => {
//Send emails with all data once contact fetch complete
console.log("Success fetching contacts - send emails.");
sendGridGroupMessages(emailDataArray);
return { success : true, message: "Success sending emails" };
})
});
function sendGridGroupMessages(emailDataArray) {
console.log('Send emails to group function with data: ' + emailDataArray.length);
var i,j, splitArray,chunk = 998;
for (i=0,j=emailDataArray.length; i<j; i+=chunk) {
splitArray = emailDataArray.slice(i,i+chunk);
// do whatever
//Send emails
sgMail.send(splitArray, (error, result) => {
if (error) {
//Do something with the error
console.log("Error sending group emails: " + error);
// throw new functions.https.HttpsError('internal', 'There was an error sending the group emails - ' + error.message + ' (' + error.code + ')');
} else {
//Celebrate
console.log("Success sending group emials: " + JSON.stringify(result));
// return { success : true };
}
});
}
You must post the error log
If no errors it means they sent
but check SendGrid activities to see what happened to these emails

How to load all sql output data into html table

It is a simple email blast program which I build
I stuck in receiving data in a loop.
I want to print all the data from the result but i can able to print last data in the result.
Here is my code:
var mailer = require("nodemailer"),
config = require('./config'),
redshiftClient = require('./redshift.js'),
sql = require('./main');
var smtpTransport = mailer.createTransport({
service: "Outlook",
auth: {
user: config.user,
pass: config.passwd
}
});
console.log("Application started at : "+ new Date().getTime())
sql.log();
redshiftClient.query(getQuery())
.then(function (data) {
console.log("Query executed at " + new Date().getTime())
var element = data.rows[index];
for (let index = 0; index < data.rows.length; index++) {
var element = data.rows[index];
var mail = {
from: 'xxxxxxx#outlook.com',
to: 'xxxxx#gmail.co.in',
subject: `Daily AWS validation check for the ${new Date().getTime()}`,
html: `<h1> Hello Team, Here is the hourly data pull</h1> <br> <table><tr> <th>Hoursdata</th> <th>Book_Counts</th> <th>Search_Counts</th> <th>Hit_Counts</th> <tr><td>${element.run_hour}</td> <td>${element.book_counts}</td> <td>${element.search_counts}</td> <td>${element.hit_counts}</td></tr> </table>`
}
smtpTransport.sendMail(mail, function (error, response) {
if (error) {
console.log(error);
} else {
console.log("Email sent");
}
smtpTransport.close();
});
}
})
.catch(function (err) {
console.error(err);
});
redshiftClient.close();
My Output for the code: (It gave the last value)
Hoursdata Book_Counts Search_Counts Hit_Counts
2 2131 5645654 4355
Expected output: (I need all the value)
Hoursdata Book_Counts Search_Counts Hit_Counts
1 23425 457474 2534
2 2131 5645654 4355
What you are trying to do is, iterate over all the rows and for each row send a message, while you should be creating a mail message once with all the details in html. As suggested in my previous answer, use some npm package to create html and pass it to html while creating message and after that send email.

Nodejs - Google API Send email doesn't group by thread

As part of small email CRM project, i have created a Nodejs app. While i send email through gmail send api, the messages are not grouped.
const sendObject = {}
output.data.messages.map(each => {
sendObject.threadId = each.threadId // ThreadID is unique.
each.payload.headers.map(head => {
if (head.name === 'From') {
sendObject.replyTo = head.value
} else if (head.name === 'Subject') {
if (head.value.indexOf('Re:') >= 0) {
sendObject.subject = head.value
} else {
sendObject.subject = 'Re: ' + head.value
}
} else if (head.name === 'Message-Id') {
sendObject.inReplyTo = head.value // The last thread messageId is inserted into In-Reply-To tag
sendObject.reference.push(head.value) // All the messageId are appended as part of References tag
}
})
})
const email_lines = []
email_lines.push('References:' + sendObject.reference.join(' '))
email_lines.push('In-Reply-To:' + sendObject.inReplyTo)
email_lines.push('Subject: ' + sendObject.subject)
email_lines.push('To:' + sendObject.replyTo)
email_lines.push('')
email_lines.push(req.body.body)
const email = email_lines.join('\r\n').trim()
let base64EncodedEmail = new Buffer(email).toString('base64')
base64EncodedEmail = base64EncodedEmail.replace(/\+/g, '-').replace(/\//g, '_')
emailConfig.gmail.users.messages.send({
userId: 'me',
threadId: sendObject.threadId,
resource: {
raw: base64EncodedEmail
}
}, async (err) => {
if (err) {
console.log('The Threads API returned an error: ' + err)
}
})
When i login to gmail through browser, i can see 2 different emails instead of one thread.
You should put threadId inside of the resource.
const response = await this.gmail.users.messages.send({
auth: this.oAuth2Client,
userId: this.gmailAddress,
uploadType: 'multipart',
resource: {
threadId: email.threadId,
raw: raw
}
})
For more details, you can check my Gmail-APIs-wrapper here

Node.js call back function on termination of a user defined function

I have a node.js app consisting of a timer calling a user defined function made up of a bunch of functions in node language. The calling script has a timer calling function mybuy() every 10 seconds; mybuy() buys crypto currencies using Binance api according trigger prices contained in a mySQL table (alarms). I would like to start mysell() (not shown , but similar to myBuy()) right after mybuy() has run its course.
How to make mysell() the callback function of mybuy()?
This the calling script:
var fs = require('fs');
var sl = require('alberto/buy');
var loop = 0;
setImmediate(() => {
// start the log
fs.appendFile('./log.txt', "\n Loop-> " + loop + "\n", function (err) {
if (err) { console.log(err); }
})
//execute the function
sl.mybuy(); // USD function; everything happens here.Can take long to finish
var myInt = setInterval(function () {
loop++;
fs.appendFile('./log.txt', "Loop-> " + loop + "\n", function (err) {
if (err) { console.log(err); }
})
//execute every 10 secs
sl.mybuy();
if (loop > 5) { clearInterval(myInt); } // max 6 loops for testing
}, 10000);
});
the UDF id here
exports.mybuy = function () {
var fs = require('fs'); // I keep a log.txt
process.stdout.write("\u001b[2J\u001b[0;0H");// clear screen
aww = (new Date()).toJSON().slice(0, 19).replace(/[-T]/, '-');
aww = aww.replace(/T/, ' ');
console.log(aww, '\n\n'); // practicing with dates
var mysql = require('mysql');
var con = mysql.createConnection({
host: "www.photobangkok.com",
user: "photoban_user",
password: "xxxxxxxx",
database: "photoban_datab"
});
// 'added' is for never processed entries in alarms table.It will change to BOUGHT or SOLD
sql = "SELECT rec, id,coin,buy,amount_b,stat FROM alarms where stat='added' AND buy>0 order by coin";
var cnt = 0; // not used, perhaps an idea to emit an event when cnt reaches the number of rows
con.query(sql, function (err, result) {
if (err) throw err;
str = "";
for (var index in result) {
str = result[index].rec + "-" + result[index].id + "-" + result[index].coin + "-" + result[index].buy + "-" + result[index].amount_b + "-" + result[index].stat;
// set up variables
coin = result[index].coin;
buy = result[index].buy;
rec = result[index].rec;
id = result[index].id;
amount = result[index].amount_b;
console.log('\x1b[36m%s\x1b[0m', str); // set color green. Display str
checkprice(coin, buy, rec, id, amount); //check Binance today price for the coin.The function will execute sometimes
} // end of loop
console.log('\x1b[36m%s\x1b[0m', str); // set color green. Display str
});
//check single coin price using binance api
function checkprice(coin, buy, rec, id, amount) {
const binance = require('node-binance-api')().options({
APIKEY: '<key>',
APISECRET: '<secret>',
useServerTime: true,
test: true //sandbox does not work
});
binance.prices(coin, (error, ticker) => {
act = "Nothing"; // default value
pricenow = ticker[coin]; // note ticker[coin]
if (pricenow < buy) {
show(id, rec, coin, buy, amount, pricenow);// Display sometimes then call book()
} else { console.log(coin, pricenow, buy, act, '\n'); }
});
}
function show(id, rec, coin, buy, amount, pricenow) {
delta = buy - pricenow; // posted trigger - today price
delta = delta.toFixed(8);
console.log('\x1b[31m%s\x1b[0m', coin, buy, amount, id, rec, ">BUY", delta); //display entries from alarms higher that today price
book(id, rec, coin, buy, amount, pricenow);
}
// dummy function to be replaced with a buy api order
function book(id, rec, coin, buy, amount, pricenow) {
const binance = require('node-binance-api')().options({
APIKEY: '<key>',
APISECRET: '<secret>',
useServerTime: true,
test: true //sandbox
});
console.log("Order:buy what??", coin, "amount:", amount, '\n');
/* binance.prices(coin, (error, ticker) => {
console.log("booking",coin, ticker[coin]);
update(id,rec);
}); */
update(id, rec, amount); // update mySql table. Slow but sure
}
function update(id, rec, amount) {
var sql = "UPDATE alarms SET stat = 'BOUGHT' ,today =
CONVERT_TZ(now(), '+00:00', '+7:00') WHERE id = "+id+" AND rec = "+rec;
con.query(sql, function (err, result) {
if (err) throw err;
console.log(result.affectedRows + " record updated");
// keep a log.tx
fs.appendFile('./log.txt', aww + " bought " + id + "-" + rec + "-" + amount + "\n",
function (err) {
if (err) { console.log(err); }
})
});
}
// I could check if all rows are done and raise an event? (how to do it)
} // end
To make mySell as the callback method of myBuy, invoke the myBuy method using the following structure.
myBuy(() => {
// operation of mySell method
});
And your myBuy method should return the callback, after perform its own operation.
exports.myBuy = function(cb) {
// operation of myBuy method
return cb; // return to the mySell method
}

Trouble with asynchronous requests pulling data from SQL database in Microsoft Bot Framework

I have a bot in the Microsoft bot Framework that I want to be able to pull data from an azure SQL database in order to answer questions asked to the bot. I have set up the database and it has some excel files in it.
Here is my code right now:
var Connection = require('tedious').Connection;
var Request = require('tedious').Request;
var connection = new Connection(dataconfig);
connection.on('connect', function(err) {
console.log("Connected");
executeStatement();
});
var Request = require('tedious').Request;
var TYPES = require('tedious').TYPES;
function executeStatement() {
request = new Request("select \"Product Name\" from SPA_Data_Feeds where \"Strategic Priority\" = 'Accelerate to Value (LD)'",
function(err, rowCount, rows)
{
console.log(rowCount + ' row(s) returned');
}
);
var result = "";
var count = 0
request.on('row', function(columns) {
columns.forEach(function(column) {
console.log("%s\t", column.value);
result+= column.value + "\t\n";
count++;
if ( count == rowCount ) {
ATVData(result);
} ;
});
});
connection.execSql(request);
}
function ATVData(result) { //Puts "result" inside of an adaptive card }
I cant seem to figure out how to get the if statement right. rowCount does not work because it does not wait for it to be defined by the function before first, and I have tried using things like column(s).length, result(s).length but none work.
Is there something else I could use that would complete the if statement? Or do I need to reformat somehow with callbacks/promises to get it to wait for rowCount to be defined? If so could I get some advice on that?
Is there something else I could use that would complete the if statement? Or do I need to reformat somehow with callbacks/promises to get it to wait for rowCount to be defined? If so could I get some advice on that?
We can use Q.js which is one of the JavaScript Promise implementation to solve this issue. For example:
var Connection = require('tedious').Connection;
var Request = require('tedious').Request;
var q = require('q');
// Create connection to database
var config =
{
userName: '', // update me
password: '', // update me
server: '', // update me
options:
{
database: '' //update me
, encrypt: true
}
}
var connection = new Connection(config);
// Attempt to connect and execute queries if connection goes through
connection.on('connect', function(err)
{
if (err)
{
console.log(err)
}
else
{
queryDatabase().then(function(result){
ATVData(result);
}, function(err){
console.log(err);
});
}
}
);
function queryDatabase()
{
console.log('Reading rows from the Table...');
//create a promise
var deferred = q.defer();
// Read all rows from table
var result = [];
var request = new Request(
"SELECT * From ForumMessages",
function(err, rowCount)
{
deferred.resolve(result);
});
request.on('row', function(columns) {
columns.forEach(function(column) {
console.log("%s\t%s", column.metadata.colName, column.value);
result.push(columns);
});
});
connection.execSql(request);
//return the promise
return deferred.promise;
}
function ATVData(result){
//your bot code goes here
}
I think to expand on Grace's answer, for each row, you can also do this for some utility:
request.on('row', function(columns) {
var singleResult = {};
columns.forEach(function(column) {
console.log("%s\t%s", column.metadata.colName, column.value);
// Add a property to the singleResult object.
singleResult[column.metadata.colName] = column.value;
// Push the singleResult object to the array.
result.push(singleResult);
});
});
Then you can, in your bot's code, call each object by the property name in dot notation, for example: result[x].colName where colName is the name of the column (or object property in this case).
Example (assuming at least one result item from the database, with a "link" column that has data):
var adaptiveCardExample = {
'contentType': 'application/vnd.microsoft.card.adaptive',
'content': {
'$schema': 'http://adaptivecards.io/schemas/adaptive-card.json',
'type': 'AdaptiveCard',
'version': '1.0',
'body': [
{
"type": "TextBlock",
"text": "Code Example"
},
{
"type": "TextBlock",
"text": "We're going to " + result[0].link,
"wrap": true
}],
'actions': [
{
'type': 'Action.OpenUrl',
'title': 'Go to the example link',
'url': result[0].link
}
]
}
};
var adaptiveCardMsg = new builder.Message(session).addAttachment(adaptiveCardExample);
session.send(adaptiveCardMsg);
You may want to add a check for null or undefined for the property in the case it is a nullable field in the database, as a precaution.

Resources