Nodemailer's sendMail function returns "undefined" - javascript

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

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

aws upload object to S3 bucket and pass details of data to lambda

Working my way through tutorials for AWS...So ive created an S3 bucket which when a file is dropped into it calls my lambda 'testHelloWorld' which sends an email...this all works fine (see below)
'use strict';
console.log('Loading function');
var aws = require('aws-sdk');
var ses = new aws.SES({
region: 'us-west-2'
});
exports.handler = function(event, context) {
console.log("Incoming: ", event);
// var output = querystring.parse(event);
var eParams = {
Destination: {
ToAddresses: ["johnb#hotmail.com"]
},
Message: {
Body: {
Text: {
Data: "Hey! What is up?"
}
},
Subject: {
Data: "Email Subject!!!"
}
},
Source: "johnb#hotmail.com"
};
console.log('===SENDING EMAIL===');
var email = ses.sendEmail(eParams, function(err, data){
if(err) console.log(err);
else {
console.log("===EMAIL SENT===");
console.log(data);
console.log("EMAIL CODE END");
console.log('EMAIL: ', email);
context.succeed(event);
}
});
};
but I want to extend the email to include data on the file that was uploaded to the bucket. I have found How to trigger my Lambda Function once the file is uploaded to s3 bucket which gives a node.js code snippet which should capture the data. I have tried to import this into my existing lambda
'use strict';
console.log('Loading function');
var aws = require('aws-sdk');
var ses = new aws.SES({
region: 'us-west-2'
});
var s3 = new aws.S3({ apiVersion: '2006-03-01', accessKeyId: process.env.ACCESS_KEY, secretAccessKey: process.env.SECRET_KEY, region: process.env.LAMBDA_REGION });
exports.handler = function(event, context, exit){
console.log("Incoming: ", event);
// var output = querystring.parse(event);
// Get the object from the event and show its content type
// const bucket = event.Records[0].s3.bucket.name;
// const key = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' '));
const params = {
Bucket: 'bucketName',
Key: 'keyName',
Source : 'SourceName',
Destination : 'DestinationName',
Message : 'MessageName'
};
s3.getObject(function(err, data){
if (err) {
console.log('ERROR ' + err);
// exit(err);
} else {
// the data has the content of the uploaded file
var eParams = {
Destination: {
ToAddresses: ["johnboy#hotmail.com"]
},
Message: {
Body: {
Text: {
Data: data
}
},
Subject: {
Data: "Email Subject!!!"
}
},
Source: "johnboy#hotmail.com"
};
}
});
console.log('===SENDING EMAIL===');
var email = ses.sendEmail(eParams, function(err, data){
if(err) console.log(err);
else {
console.log("===EMAIL SENT===");
console.log(data);
console.log("EMAIL CODE END");
console.log('EMAIL: ', email);
context.succeed(event);
}
});
};
but this is failing on the params
message: 'There were 3 validation errors:
* MissingRequiredParameter: Missing required key \'Source\' in params
* MissingRequiredParameter: Missing required key \'Destination\' in params
* MissingRequiredParameter: Missing required key \'Message\' in params',
code: 'MultipleValidationErrors',
errors:
These source, destination and message are listed in the params, are they not correctly formatted and it isnt picking them up?
I cant find much online....any help appreciated
UPDATE
Ok iv got it working without failing...if i use the test function in the lambda with the following code...
'use strict';
console.log('Loading function');
var aws = require('aws-sdk');
var ses = new aws.SES({
region: 'us-west-2'
});
var s3 = new aws.S3({ apiVersion: '2006-03-01', accessKeyId: process.env.ACCESS_KEY, secretAccessKey: process.env.SECRET_KEY, region: process.env.LAMBDA_REGION });
exports.handler = function(event, context) {
console.log("Incoming: ", event);
// var output = querystring.parse(event);
var testData = null;
// Get the object from the event and show its content type
// const bucket = event.Records[0].s3.bucket.name;
// const key = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' '));
const params = {
Bucket: 'bucket',
Key: 'key',
};
s3.getObject(params, function(err, data){
if (err) {
console.log('ERROR ' + err);
exit(err);
} else {
testData = data;
}
});
var eParams = {
Destination: {
ToAddresses: ["jim#him.com"]
},
Message: {
Body: {
Text: { Data: 'testData2' + testData}
},
Subject: {
Data: "Email Subject!!!"
}
},
Source: "jim#him.com"
};
console.log('===SENDING EMAIL===');
var email = ses.sendEmail(eParams, function(err, data){
if(err) console.log(err);
else {
console.log("===EMAIL SENT===");
console.log(data);
console.log("EMAIL CODE END");
console.log('EMAIL: ', email);
context.succeed(event);
}
});
};
I get the email with the body- testData2null
So I tried uploading an image through the s3 bucket and I still get the email with the body testData2null
is there anyway to debug this further or does anyone kno who it is saying null. I never actually tested the code from the other post which passes the data over to the email I just assumed it would work. Does anyone else know who to obtain the data from the upload please? thanks
You are declaring the var eParams within the callback of s3.getObject, but then you run the ses.sendMail outside of the callback. I think that's why!
You also need to move the ses.sendEmail to inside the callback of s3.getObject if you want to send the data from your object inside the email.
Try this:
s3.getObject(function(err, objectData) {
if (err) {
console.log('Could not fetch object data: ', err);
} else {
console.log('Data was successfully fetched from object');
var eParams = {
Destination: {
ToAddresses: ["johnboy#hotmail.com"]
},
Message: {
Body: {
Text: {
Data: objectData
}
},
Subject: {
Data: "Email Subject!!!"
}
},
Source: "johnboy#hotmail.com"
};
console.log('===SENDING EMAIL===');
var email = ses.sendEmail(eParams, function(err, emailResult) {
if (err) console.log('Error while sending email', err);
else {
console.log("===EMAIL SENT===");
console.log(objectData);
console.log("EMAIL CODE END");
console.log('EMAIL: ', emailResult);
context.succeed(event);
}
});
}
});
You need to read on how Nodejs works. It is event based and depends on callbacks and promises. You should do -
s3.getObject(params, function(err, data){
//This is your callback for s3 API call. DO stuff here
if (err) {
console.log('ERROR ' + err);
exit(err);
} else {
testData = data;
// Got your data. Send the mail here
}
});
I have added my comments in code above. Since Nodejs is single threaded it will make S3 api call and go ahead. When it is sending mail s3 api call is not complete so data is null. It is better to use promises here.
Anyway read up on callback and promises in nodejs and how it works. But hope this answers your logical error.

Async - Can't set headers after they are sent

I am new to node and async...
I am getting an error saying I can't set headers after they sent when I am sending a response back to api-ai
Any idea why?
Below is the code for function - getUserFirstName(userId, name, callback):
var name = "";
function getUserFirstName(userId, name, callback) {
console.log('withParams function called');
request({
method: 'GET',
uri: "https://graph.facebook.com/v2.6/" + userId + "?fields=first_name,last_name,profile_pic,locale,timezone,gender&access_token=" + FB_PAGE_ACCESS_TOKEN
},
function (error, response) {
if (error) {
console.error('Error while userInfoRequest: ', error);
} else {
if(!typeof response.body != 'object'){
var body = JSON.parse(response.body);
name = body.first_name;
callback(null,name);
}else{
name = response.body.first_name;
callback(null,name);
}
}
});
}
Here is the code being executed:
app.post('/webhook/', (req, res) => {
var data = JSONbig.parse(req.body);
var action = data.result.action;
var facebook_message = [];
if(action == "input.welcome"){
var userId = data.originalRequest.data.sender.id;
async.series([
function(callback) {
getUserFirstName(userId, name, callback);
}
], function(err,results) {
if (results != undefined){ // results = "John"
facebook_message = [{
"text":"Heyyyoo. Welcome!"
}]
}else{
facebook_message = [{
"text":"Hey " + results +"! Welcome!" // Hey John! Welcome!
}]
}
res.json({ // line 308 - error here!
speech: "Greetings",
displayText: "Greetings",
"data": {
"facebook": facebook_message
},
source: "webhook"
});
});
}
// BUNCH OF LONG AND MESSY CODES OVER HERE...
return res.status(200).json({
status: "ok"
});
Error
Error: Cant set headers after they are sent.
at ServerResponse.OutgoingMessage.setHeader (_http_outgoing.js:356:11)
at ServerResponse.header (/app/node_modules/express/lib/response.js:719:10)
at ServerResponse.send (/app/mode_modules/express/lib/response.js:164:12)
at ServerRespose.json (/app/mode_modules/express/lib/response.js:250:15)
at /app/src/app.js: 308:15
at /app/node_modules/async/dist/async.js:3694:9
at /app/node_modules/async/dist/async.js:356:16
at replenish (/app/node_modules/async/dist/async.js:877.25)
at iterateeCallback (/app/node_modules/async/dist/async.js:867:17)
at /app/node_modules/async/dist/async.js:840:16
Remove the following:
return res.status(200).json({
status: "ok"
});

node.js sending mails with a delay

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

Node js custom callback function error

I'm trying to make a simple authentication with node js. Because I read user data from a database, I have to make it asynchronous. Here's my function, which checks if authentication is ok:
function auth(req, callback) {
var header = req.headers['authorization'];
console.log(cb.type);
console.log("Authorization Header is: ", header);
if(!header) {
callback(false);
}
else if(header) {
var tmp = header.split(' ');
var buf = new Buffer(tmp[1], 'base64');
var plain_auth = buf.toString();
console.log("Decoded Authorization ", plain_auth);
var creds = plain_auth.split(':');
var name = creds[0];
var password = creds[1];
User.findOne({name:name, password:password}, function(err, user) {
if (user){
callback(true);
}else {
callback(false);
}
});
}
}
And here I call the function:
auth (req, function (success){
if (!success){
res.setHeader('WWW-Authenticate', 'Basic realm="myRealm');
res.status(401).send("Unauthorized");
}else{
if(user!==req.user) {
res.status(403).send("Unauthorized");
}else{
User.findOneAndUpdate({user:userid}, {user:req.body.user, name:req.body.name, email:req.user.email, password:User.generateHash(req.body.password)},
{upsert:true}, function(err, user) {
if(!err) {
res.status(200).send("OK");
}else{
res.status(400).send("Error");
}
});
}
}
});
This gives me error "TypeError: object is not a function", pointing at "callback(false)". I have no idea what could cause this error, as I pass a function as a parameter, and the first log message prints "[function]". Any help would be appreciated.

Categories

Resources