0

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:

controller function

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 => {
        res.send(data);
    }).catch(err => {
        res.status(500).send({
            message: err.message || "Some error occurred while creating the listing."
        });
    });
};

NodeMailer functon

 var smtpTransport = nodemailer.createTransport({
        service: 'Gmail',
        port: 465,
        auth: {
          user: 'YOUR_GMAIL_SERVER',
          pass: 'YOUR_GMAIL_PASSWORD'
        }
      });

      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) {
            res.send(error)
          } else {
            res.send('Success')
          }
          smtpTransport.close();
        });

How can I include this Nodemailer part inside the above create listing function also how can I include that submited data inside email body. I assume current data.title and other options inside email body are wrong way.

NewTech Lover
  • 1,185
  • 4
  • 20
  • 44

4 Answers4

1

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.

Neil Lunn
  • 148,042
  • 36
  • 346
  • 317
  • ${data.title}

    ${data.city}

    ${data.street}

    `, Is this data getting from the above saveed Listing details?
    – NewTech Lover Oct 03 '19 at 10:04
  • @NewTechLover Yes. That's why it's inline right after the `save()`. That resolves with `data` and then your set up of the mail content has access to `data` and all it's properties. Also why I explained you `resolve(data)` for the same reason the next `then()` has access to the same `data` value. – Neil Lunn Oct 03 '19 at 10:08
  • Neil I get similar issue after placing data from 2nd model in the same function: What if data should be added from 2 models? https://stackoverflow.com/questions/58267043/how-to-fix-this-node-controller-function-and-send-data-from-2-different-models – NewTech Lover Oct 07 '19 at 09:52
0

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

Kishan Maurya
  • 3,356
  • 8
  • 21
  • Hi, the point is that I need to place the submited data e.g. title: req.body.title, city: req.body.city, street: req.body.street, businessname: req.body.businessname, description: req.body.description Inside email body. – NewTech Lover Oct 03 '19 at 09:55
0

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."
        });
    }
};
Dan Starns
  • 3,765
  • 1
  • 10
  • 28
  • `sendMail` does not return a Promise. At least it does not in it's native form from nodemailer. So you cannot just add `return smtpTransport.sendMail(`. You also appear to have misunderstood what the `async` keyword does. It does not make something "async" or a Promise. It merely allows usage of `await` on the inner block it is declared on. – Neil Lunn Oct 03 '19 at 10:00
  • await sendMail({ I guess there was a mistake right? – NewTech Lover Oct 03 '19 at 10:04
  • @NeilLunn [nodemailers documentaion](https://nodemailer.com/about/) in the 'example' you can clearly see `await transporter.sendMail({...})` You can also so on [node mailer usage docs](https://nodemailer.com/usage/), at the bottom, "If callback argument is not set then the method returns a Promise object." Finally, I don't understand your comment on the [`async/await`](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await) usage, using the async keyword infont of a function will "tell them to return a promise rather than directly returning the value" – Dan Starns Oct 03 '19 at 10:08
  • @NewTechLover You need to import `sendEmail` function into your controller ive added into the snippet. – Dan Starns Oct 03 '19 at 10:09
  • 2
    Fair enough. That probably should be more prominent in the documentation considering all other actual usage examples "at a glance" just show the `callback` usage. – Neil Lunn Oct 03 '19 at 10:13
0

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();
        });
});
};
ArUn
  • 1,317
  • 2
  • 23
  • 39