Stuck in a express callback hell - node.js

I can't cancel the request after sending the email successfully. How can I return properly after sending the email? This might be a callback hell, but I cant figure out how to solve it.
I tried to put some return in different parts but it didn't work.
const router = require('express').Router();
const nodemailer = require('nodemailer');
const emailExistence= require('email-existence');
module.exports = router;
// Send email when user has forgotten his/her password
router.post('/forgetPass', (req, res, next) => {
if(!req.body.email){
next(new Error("Email is required."));
return;
}
emailExistence.check(req.body.email, function(err,res){
if(err || !res){
next(new Error("The email does'nt exist."));
return;
}else{
let transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: 'myemail#gmail.com',
pass: 'mypassword'
}
});
let mailOptions = {
from: 'myemail#gmail.com',
to: req.body.email,
subject: 'Link for setting a new password',
html: 'Set a new password'
text: 'email text'
};
transporter.sendMail(mailOptions, function(error, info){
if (error) {
next(new Error("Error in sending email."));
return;
}
res.json(Object.assign(req.base, {
message: "The email has been sent successfully.",
data: info
}));
return;
});
}
});
});

Once you set your response field on successful sending, call next() as a last step, so the next middleware gets the request and sends the response back. So basically:
...
res.json(yourResponse);
next();
...
Or, if this is the last middleware, send the response back to client:
res.send(yourResponse);

I solved it in this way. The emailExistence didn't let me to use promises, so I used email-ckeck instead of it:
const router = require('express').Router();
const nodemailer = require('nodemailer');
const emailExistence= require('email-existence');
var emailCheck = require('email-check');
module.exports = router;
router.post('/forgetPass', (req, res, next) => {
if(!req.body.email){
next(new Error("Email is required."));
return;
}
// Check the req.body.email with email pattern regex
var patt = new RegExp (process.env.EMAIL_PATTERN__REGEX),
isEmail = patt.test(req.body.email);
if(!isEmail){
next(new Error("The email does'nt seem to be a valid email. If you are sure about your email validity contact the website admin."));
return;
}
return emailCheck(req.body.email)
.then(function(result){
let transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: process.env.EMAIL,
pass: process.env.EMAILPASSWORD
}
});
let mailOptions = {
from: process.env.EMAIL,
to: req.body.email,
subject: 'Link for setting a new password',
html: 'Set a new password from this link.'
};
return transporter.sendMail(mailOptions)
.then(function (result2) {
res.status(200).json(Object.assign(req.base, {
message: "The email has been sent successfully.",
data: null
}));
return;
},
function(error2){
next(new Error("Error in sending email."));
return;
});
},
function(error) {
next(new Error("The email does'nt seem to be a valid email. If you are sure about your email validity contact the website admin."));
return;
});
});

Related

Sending an email to a user after submitting an enquiry

I'm trying to find a way to set up a contact us form, when a user is submitting an enquiry I should receive an email, also the user should receive an email telling that the enquiry reached us, I figured out that I should use nodemailer to send mails, now the first step is done which I receive an email whenever the user submitted an enquiry, the question is how can I send him an email after submitting the enquiry? excuse my bad English
Nodemailer setup
//email from the contact form using the nodemailer
router.post('/send', async(req, res) => {
console.log(req.body);
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: process.env.EMAIL_USERNAME, //email resposible for sending message
pass: process.env.EMAIL_PASSWORD
}
});
const mailoptions = {
from: process.env.FROM_EMAIL,
to: process.env.EMAIL_USERNAME, //email that recives the message
subject: 'New Enquiry',
html: `<html><body><p>Name: ${req.body.name}</p><p> Email: ${req.body.email}</p><p>message: ${req.body.content}</p></body></html>`
}
transporter.sendMail(mailoptions, (err, info) => {
if (err) {
console.log('error');
res.send('message not sent'); //when its not ent
} else {
console.log('message sent' + info.response);
res.send('sent'); //when sent
}
})
});
module.exports = router;
So I tried some codes and code worked for me, it's sending to the admin and the client, hope it helps
router.post('/send', async(req, res, next) => {
console.log(req.body);
if (!req.body.name) {
return next(createError(400, "ERROR MESSAGE HERE"));
} else if (!req.body.email) {
return next(createError(400, "ERROR MESSAGE HERE"));
} else if (!req.body.email || !isEmail(req.body.email)) {
return next(createError(400, "ERROR MESSAGE HERE"));
} else if (!req.body.content) {
return next(createError(400, "ERROR MESSAGE HERE"));
}
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: process.env.EMAIL_USERNAME, //email resposible for sending message
pass: process.env.EMAIL_PASSWORD
}
});
const mailOptionsAdmin = {
from: process.env.FROM_EMAIL,
to: process.env.EMAIL_USERNAME, //email that recives the message
subject: 'New Enquiry',
html: `<html><body><p>Name: ${req.body.name}</p><p> Email: ${req.body.email}</p><p>message: ${req.body.content}</p></body></html>`
}
const mailOptionsClient = {
from: process.env.FROM_EMAIL,
to: req.body.email, //email that recives the message
subject: 'Recivied',
html: `<html><body><p>message: We recived your enquiry</p></body></html>`
}
transporter.sendMail(mailOptionsAdmin, (err, info) => {
if (err) {
console.log('error');
res.send('message not sent'); //when its not ent
} else {
console.log('message sent' + info.response);
res.send('sent'); //when sent
transporter.sendMail(mailOptionsClient)
}
})
});

Nodemailer sending emails but getting ERR_HTTP_HEADERS_SENT

I don't why i'm getting this every time I try to use Nodemailer to send emails:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent
to the client
at new NodeError (node:internal/errors:371:5)
at ServerResponse.setHeader (node:_http_outgoing:576:11)
the thing is it's working but i'm getting this error, by working I mean It sends the email.
The Code:
const sendEmail = async (email, subject, payload, template) => {
try {
// create reusable transporter object using the default SMTP transport
const transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: process.env.EMAIL_USERNAME,
pass: process.env.EMAIL_PASSWORD,
},
});
const options = () => {
return {
from: process.env.FROM_EMAIL,
to: email,
subject: subject,
html: template,
};
};
// Send email
transporter.sendMail(options(), (error, info) => {
if (error) {
return error;
} else {
return res.status(200).json({
success: true,
});
}
});
} catch (error) {
throw error;
}
};
router.post("/", async (req, res, next) => {
try {
if (!req.body.newsletterEmail) {
return next(createError(400, "Please enter your email"));
} else if (!req.body.newsletterEmail || !isEmail(req.body.newsletterEmail)) {
return next(createError(400, "Please enter a valid email address"));
}
const checkEmail = await SubscribedEmails.findOne({ newsletterEmail: req.body.newsletterEmail });
if (checkEmail) {
return next(createError(400, "Email is already subscribed"));
} else {
//create new user
const newSubscribedEmail = new SubscribedEmails ({
newsletterEmail: req.body.newsletterEmail,
});
//save user and respond
const SubscribedEmail = await newSubscribedEmail.save();
res.status(200).json({
message: "Successfully subscribed",
SubscribedEmail,
});
const link = `${process.env.CLIENT_URL}/`;
await sendEmail(
SubscribedEmail.newsletterEmail,
"Welcome to our newsletter!",
{
link: link,
},
`<div> <p>Hi,</p> <p>You are subscribed to our.</p> <p> Please click the link below to view more</p> <a href=${link}>GO</a> </div>`
);
return res.status(200).send(link);
}
} catch(err) {
next(err);
}
});
This is likely due to some quirks with using Gmail with Nodemailer. Review the docs here - https://nodemailer.com/usage/using-gmail/
and configure your Gmail account here -
https://myaccount.google.com/lesssecureapps
Finally, you may run into issues with Captcha. Configure here - https://accounts.google.com/DisplayUnlockCaptcha

Nodemailer error: verification email doesn't works

I am writing this code to sign up the user.
I used Nodemailer to send a verification email to activate the account.
The post request works well but I did not receive a verification email and here is my code:
const transporter = nodemailer.createTransport({
service: "Gmail",
auth: {
user: process.env.EMAIL_USERNAME,
pass: process.env.EMAIL_PASSWORD,
},
});
exports.signup = async(req, res) => {
const { email } = req.body
if (!email) {
return res.status(422).send({ message: "Missing email." })
}
try {
const existingUser = await User.findOne({ email }).exec();
if (existingUser) {
return res.status(409).send({
message: "Email is already in use."
});
}
const user = await new User({
_id: new mongoose.Types.ObjectId,
email: email
}).save();
const verificationToken = user.generateVerificationToken();
const url = `http://localhost:5000/api/verify/${verificationToken}`
transporter.sendMail({
to: email,
subject: 'Verify Account',
html: `Click <a href = '${url}'>here</a> to confirm your email.`
})
return res.status(201).send({
message: `Sent a verification email to ${email}`
});
} catch (err) {
return res.status(500).send(err);
}
}
WHEN I SEND THE REQUEST, SHOWS ME LIKE THIS
Go to Google account, than Security and Less secure app access, and set to ON. Restart Your app and Now should up and running ;-)
Note That - In Your .env file, enter Your credentials without any quotes

Unable to send mail through node mailer. Error: connect ECONNREFUSED at port 25"

Every time I make a forgot password request, it gives me this error and the mail is never sent to the Mailtrap.
Nodemailer Setup
This is the basic node mailer setup I did.
port = 25
const nodemailer = require('nodemailer');
const sendEmail = async (options) => {
// 1) Create a Transporter
const transporter = nodemailer.createTransport({
host: process.env.EMAIL_HOST,
port: process.env.EMAIL_PORT,
auth: {
user: process.env.EMAIL_USERNAME,
pass: process.env.EMAIL_PASSWORD,
},
});
// 2) Define the email Options
const mailOptions = {
from: 'Sachin Yadav <sachin.yadav#gmail.com',
to: options.email,
subject: options.subject,
text: options.text,
};
// 3) Send the email
await transporter.sendMail(mailOptions);
};
Forgot Password Function
catchAsync is just a wrapper function which is created just to catch asynchronous errors separately. It returns the async function passed into it and call it with the req, res and next parameters.
exports.forgotPassword = catchAsync(async (req, res, next) => {
// 1) Get user based on posted email
const user = await User.findOne({ email: req.body.email });
if (!user)
return next(
new AppError('No user with that email. Please try again!', 404)
);
// 2) Generate Random
const resetToken = user.createPasswordResetToken();
await user.save({ validateBeforeSave: false });
// 3) Send back the token on email
const resetURL = `${req.protocol}://${req.get('host')}/api/v1/users/resetPassword/${resetToken})}`;
const message = `Forgot your password? Submit a PATCH request with your new password and passwordConfirm to : ${resetURL}`;
// Send Email
try {
await sendEmail({
email: user.email,
subject: 'Password reset link (Valid for 10mins)',
message,
});
res.status(200).json({
status: 'success',
message: 'Token send',
});
// Err
} catch (err) {
// Set back the token and expire time
user.createPasswordResetToken = undefined;
user.passwordResetExpires = undefined;
await user.save({ validateBeforeSave: false });
return next(
new AppError(`There was an error sending the email ${err.message}`, 500)
);
}
});
I just changed the port from 25 to 2525 and for some reason it worked. If anyone know why this worked, please let me know.
One reason could be some other application is using port '25' in your system. If you're using a Windows PC you can check the ports that are being used with the below command(you need to open cmd as an administrator):
netstat -aon

Content for confirmation email not showing when sent through nodemailer?

In my Node.js/MERN app, I get error 250 2.0.0 OK 1590267554 o18sm275551eje.40 - gsmtp & something went wrong when I register my user for authentication and receive an email with EMPTY body. I am using the code from https://blog.bitsrc.io/email-confirmation-with-react-257e5d9de725
I can see user is added to mongodb database with confirmed set to false. Why am I not getting the complete email with confirmation?
Please find my attached code for my MERN application. I would really appreciate a reply! Thank you!
Register
Register route in users which takes you to login and on React side OnSubmit starts chain of sending and confirming email.
router.post("/register", type, function (req, res, next) {
// var tmp_path = req.file.path;
if(!req.file){
console.log("File missing");
}
/** The original name of the uploaded file
stored in the variable "originalname". **/
// var target_path = 'uploads/' + req.file.originalname;
// /** A better way to copy the uploaded file. **/
// var src = fs.createReadStream(tmp_path);
// var dest = fs.createWriteStream(target_path);
// src.pipe(dest);
// fs.unlink(tmp_path);
// src.on('end', function() { res.render('complete'); });
// src.on('error', function(err) { res.render('error'); });
// Form validation
const { errors, isValid } = validateRegisterInput(req.body);
const url = req.protocol + '://' + req.get('host')
// Check validation
if (!isValid) {
return res.status(400).json(errors);
}
//Checks email against registered emails in database
registeredemails.findOne({ email: req.body.email}).select("email").lean().then(result => {
if (!result) {
return res.status(400).json({email: "Email not provided"});
}
});
User.findOne({ email: req.body.email }).then(user =>
{
if (user) {return res.status(400).json({ email: "Email already exists" })
}
else if(!user){
const newUser = new User({
firstName: req.body.firstName,
lastName: req.body.lastName,
email: req.body.email,
password: req.body.password,
fileimg: url + '/public/' + req.file.filename
});
// // Hash password before saving in database
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(newUser.password, salt, (err, hash) => {
if (err) throw err;
newUser.password = hash;
newUser
.save()
.then(newUser =>
sendEmail(newUser.email),
templates.confirm(newUser._id)
)
.then(() => res.json({ msg: msgs.confirm }))
.catch(err => console.log(err))
}
)}
)}
else if (user && !user.confirmed) {
sendEmail(user.email, templates.confirm(user._id))
.then(() => res.json({ msg: msgs.resend })).catch(err => console.log(err))
}
// The user has already confirmed this email address
else {
res.json({ msg: msgs.alreadyConfirmed })
}
}).catch(err => console.log(err))
sendemail as used in the MEDIUM articles
const nodemailer = require('nodemailer');
const { CLIENT_ORIGIN } = require('../../config')
// const mg = require('nodemailer-mailgun-transport');
// The credentials for the email account you want to send mail from.
const credentials = {
secure: true,
service: 'Gmail',
auth: {
user: process.env.MAIL_USER,
pass: process.env.MAIL_PASS
// These environment variables will be pulled from the .env file
// apiKey: 'b61286bf9e28b149fac32220f0c7349f-e5e67e3e-00a38515',
// domain: 'sandbox0b8a7f0ebcc74c0d8161304f24909bd2.mailgun.org'
}
}
// Getting Nodemailer all setup with the credentials for when the 'sendEmail()'
// function is called.
const transporter = nodemailer.createTransport(credentials)
// exporting an 'async' function here allows 'await' to be used
// as the return value of this function.
module.exports = async (to, content) => {
// The from and to addresses for the email that is about to be sent.
const contacts = {
from: process.env.MAIL_USER,
to // An array if you have multiple recipients.
// subject: 'React Confirm Email',
// html: `
// <a href='${CLIENT_ORIGIN}/confirm/${id}'>
// click to confirm email
// </a>
// `,
// text: `Copy and paste this link: ${CLIENT_ORIGIN}/confirm/${id}`
}
// Combining the content and contacts into a single object that can
// be passed to Nodemailer.
const email = Object.assign({}, content, contacts)
// This file is imported into the controller as 'sendEmail'. Because
// 'transporter.sendMail()' below returns a promise we can write code like this
// in the contoller when we are using the sendEmail() function.
//
// sendEmail()
// .then(() => doSomethingElse())
//
// If you are running into errors getting Nodemailer working, wrap the following
// line in a try/catch. Most likely is not loading the credentials properly in
// the .env file or failing to allow unsafe apps in your gmail settings.
await transporter.sendMail(email, function(error, info){
if(error)
{
return console.log(error);
}
else
{
return console.log(info.response);
}
})
}
templates.confirm as used in Medium article
onst { CLIENT_ORIGIN } = require('../../config')
// This file is exporting an Object with a single key/value pair.
// However, because this is not a part of the logic of the application
// it makes sense to abstract it to another file. Plus, it is now easily
// extensible if the application needs to send different email templates
// (eg. unsubscribe) in the future.
module.exports = {
confirm: id => ({
subject: 'React Confirm Email',
html: `
<a href='${CLIENT_ORIGIN}/confirm/${id}'>
click to confirm email
</a>
`,
text: `Copy and paste this link: ${CLIENT_ORIGIN}/confirm/${id}`
})
}

Resources