I am trying to create a form that will send data to the Mongo DB first then will send that data through the email by Nodemailer. Here are the 2 functions:
contro
I would suggest creating a wrapper module around nodemailer, therefore you could reuse the sendEmail
function multiple times.
Make yourself a file called email-client.js
or whatever you want. In this module, you can create a closure over smtpTransport
and only export the sendEmail
function.
email-client
const nodemailer = require("nodemailer");
const smtpTransport = nodemailer.createTransport({
service: "Gmail",
port: 465,
auth: {
user: "YOUR_GMAIL_SERVER",
pass: "YOUR_GMAIL_PASSWORD"
}
});
async function sendMail({ to, subject, html }) {
return smtpTransport.sendMail({ to, subject, html });
}
module.exports = {
sendMail
};
Note: smtpTransport.sendMail
returns a Promise, that we will deal with inside your controller.
controller
First, you could import the sendEmail
function that's exported from email-client.js
, then you can use this in your controller. Note ive changed the controller to be async & prefer mongoose Model.create (makes testing a little easier).
const { sendEmail } = require("./email-client.js");
exports.createListing = async (req, res) => {
try {
if (!req.body.content) {
return res.status(400).send({
message: "Fields can not be empty"
});
}
const listing = await Listing.create({
title: req.body.title,
city: req.body.city,
street: req.body.street,
businessname: req.body.businessname,
description: req.body.description
});
await sendEmail({
to: "blabla",
subject: "blabla",
html: `<p>${listing.title}</p>
<p>${listing.city}</p>
<p>${listing.street}</p>`
});
return res.send("Success");
} catch (error) {
return res.status(500).send({
message:
error.message ||
"Some error occurred while creating the listing."
});
}
};
The most simple form here would be to just wrap the function with the callback ( the nodemailer one ) in a Promise:
exports.createListing = (req, res) => {
// Validate request
if(!req.body.content) {
return res.status(400).send({
message: "Fields can not be empty"
});
}
// Set options after the request was verified.
const smtpTransport = nodemailer.createTransport({
service: 'Gmail',
port: 465,
auth: {
user: 'YOUR_GMAIL_SERVER',
pass: 'YOUR_GMAIL_PASSWORD'
}
});
const listing = new Listing({
title: req.body.title,
city: req.body.city,
street: req.body.street,
businessname: req.body.businessname,
description: req.body.description
});
listing.save()
.then(data => new Promise((resolve, reject) => {
var mailOptions = {
to: data.email,
subject: 'ENTER_YOUR_SUBJECT',
html: `<p>${data.title}</p>
<p>${data.city}</p>
<p>${data.street}</p>`,
...
};
smtpTransport.sendMail(mailOptions,
(error, response) => {
if (error) {
reject(error);
} else {
resolve(data);
}
});
})
.then(data => {
smtpTransport.close(); // this awaited the actual send
res.send(data);
}
.catch(err => {
res.status(500).send({
message: err.message || "Some error occurred while creating the listing."
});
});
};
Note that the resolve(data)
here is effectively passing through the result to the next link in the Promise chain, which is better than nesting promise chains in the same scope just to have access to the same value. Then you also have the single point for catch()
when either of methods fail.
That said, it has been brought to attention the current API actually would return a Promise
when invoked without a callback, but then you would probably want async
and await
syntax in order to make access to things cleaner:
exports.createListing = async (req, res) => { // <-- mark block as async
// Validate request
if(!req.body.content) {
return res.status(400).send({
message: "Fields can not be empty"
});
}
// Set options after the request was verified.
const smtpTransport = nodemailer.createTransport({
service: 'Gmail',
port: 465,
auth: {
user: 'YOUR_GMAIL_SERVER',
pass: 'YOUR_GMAIL_PASSWORD'
}
});
const listing = new Listing({
title: req.body.title,
city: req.body.city,
street: req.body.street,
businessname: req.body.businessname,
description: req.body.description
});
try { // try..catch for error handling
let data = await listing.save(); // await the save
var mailOptions = {
to: data.email,
subject: 'ENTER_YOUR_SUBJECT',
html: `<p>${data.title}</p>
<p>${data.city}</p>
<p>${data.street}</p>`,
...
};
await smtpTransport.sendMail(mailOptions); // await the sendMail
smtpTransport.close(); // this awaited the actual send
res.send(data);
} catch(err) {
res.status(500).send({
essage: err.message || "Some error occurred while creating the listing."
}
};
It is also important to note that this approach is serial in execution. So here the mail is not sent unless the data is correctly saved. This may or may not be your intended case, but simply creating the wrapping Promise should at least the right direction to follow.
Export your sendMail method and import it in your controller.
controller function
let sendMail = require('your nodemailer file').sendMail;
exports.createListing = (req, res) => {
// Validate request
if(!req.body.content) {
return res.status(400).send({
message: "Fields can not be empty"
});
}
const listing = new Listing({
title: req.body.title,
city: req.body.city,
street: req.body.street,
businessname: req.body.businessname,
description: req.body.description
});
listing.save()
.then(data => {
sendMail({
title: req.body.title,
city: req.body.city,
street: req.body.street})
res.send(data);
}).catch(err => {
res.status(500).send({
message: err.message || "Some error occurred while creating the listing."
});
});
};
NodeMailer function
var smtpTransport = nodemailer.createTransport({
service: 'Gmail',
port: 465,
auth: {
user: 'YOUR_GMAIL_SERVER',
pass: 'YOUR_GMAIL_PASSWORD'
}
});
module.exports.sendmail = (data)=>{
return new Promise((resolve,reject)=>{
var mailOptions = {
to: data.email,
subject: 'ENTER_YOUR_SUBJECT',
html: `<p>${data.title}</p>
<p>${data.city}</p>
<p>${data.street}</p>`,
...
};
smtpTransport.sendMail(mailOptions,
(error, response) => {
if (error) {
reject(error);
} else {
resolve('Success');
}
smtpTransport.close();
});
});
};
Create separate mail.js or anyname.js
var config = require('../config/config.js');
var nodemailer = require('nodemailer');
var smtpTransport = nodemailer.createTransport({
service :"gmail",
host: "smtp.gmail.com",
auth :
{
user: config.email,
pass: config.password
}
});
// setup email data with unicode symbols
var mailOptions = {
from: config.email,
to: 'user to send',
subject :'message',
text :' "Hi",\n You have successfully created an account"',
html: '<b>Welcome?</b>' // html body
};
// sends mail
module.exports.sendMail = function()
{
// send mail with defined transport object
smtpTransport.sendMail(mailOptions, (error, info) => {
if (error)
{
return console.log(error);
}
console.log('Message sent: %s', info.messageId);});
}
now import this file in controller js file
var mailer = require('./mail.js');
and use it like below
mailer.sendMail()
you can pass values or params inside sendMail function and access them in mail.js file to create custom message or title or name any