Adding an Email Contact Form to a Node and Express App

Updated: March 11, 2024

Published: September 17, 2017

Contact forms are a classic web pattern to allow your visitors to email you straight from the page they're on without exposing your email address. This can be great for getting customer leads for marketing sites or soliciting feedback from your visitors. Node.js/Express apps can the Nodemailer package to add email functionality to your portfolio or small business site. This tutorial will show you how to use Nodemailer in your Node/Express app to let your users easily contact you.

Note: This site is now a Jekyll static site served with GitHub Pages. I don't use this email contact form code in production anymore, though many of my readers do. To see a contact form in the context of a working app, visit my old express-portfolio repo on GitHub.

If you haven't already got an Express app ready to add to, start with the Installing Express instructions.

Install Nodemailer

Installing Nodemailer into your project is simple with npm. In your project folder, run:

npm install nodemailer

Setup server.js

Next, in your server.js file (or whatever file you run to start your Express server), require Nodemailer near the top of your file, before setting your routes:

const nodemailer = require('nodemailer')

Now that you have Nodemailer installed in your project and you are requiring it in your server, you can access its functionality in the new route you will create.

If you haven’t, you should read the official Express routing documentation to give yourself a better understanding of how routes work in Express applications.

My server.js file is going to use the conventional Express setup shown in their routing documentation:

const express = require('express')
const app = express()

I will be using the middleware package body-parser to parse the data from the contact form we will create. Install it as a dependency:

npm install body-parser

Then near the top of server.js under your Express requirements, require body-parser and mount it with use():

const bodyParser = require('body-parser')
app.use(bodyParser.urlencoded({extended: true}))

Write a route using Nodemailer

Now your application is ready to have routes added to it. We will need a simple POST method route for Nodemailer to use when a user submits the contact form.

Here is what my POST method route looks like:

// POST route from contact form
app.post('/contact', (req, res) => {

  // Instantiate the SMTP server
  const smtpTrans = nodemailer.createTransport({
    host: 'smtp.gmail.com',
    port: 465,
    secure: true,
    auth: {
      user: GMAIL_USER,
      pass: GMAIL_PASS
    }
  })

  // Specify what the email will look like
  const mailOpts = {
    from: 'Your sender info here', // This is ignored by Gmail
    to: GMAIL_USER,
    subject: 'New message from contact form at tylerkrys.ca',
    text: `${req.body.name} (${req.body.email}) says: ${req.body.message}`
  }

  // Attempt to send the email
  smtpTrans.sendMail(mailOpts, (error, response) => {
    if (error) {
      res.render('contact-failure') // Show a page indicating failure
    }
    else {
      res.render('contact-success') // Show a page indicating success
    }
  })
})

The route contains my SMTP transport server (smtpTrans), a set of email formatting options I define (mailOpts), and a call to the sendMail function with conditional logic to display either a contact-failure or contact-success view.

You will notice I’m using Gmail as my SMTP server. Gmail is not the best solution to use with Nodemailer, but it is a free solution most people will have easy access to. The host, port, and secure values in the SMTP server are all specific to Gmail, and will need to change depending on the SMTP server settings you are dealing with if you don't use Gmail.

The GMAIL_USER and GMAIL_PASS variables in the auth: configuration are references to my own environment variables in my Node application.

const GMAIL_USER = process.env.GMAIL_USER
const GMAIL_PASS = process.env.GMAIL_PASS

Always make sure to keep information like your email address and password out of public code repositories like GitHub! For a nice npm package to help you set environment variables in Node.js, try dotenv or use Node 20's native .env file support.

Also note that in this case, GMAIL_PASS doesn’t refer to my everyday Gmail password, but to a Google application-specific password I have created.

Note that Gmail sets the authenticated user (you) as the From: email address, so you will need to manually pass the sender’s email address into the subject or body of the email if you would like it to be sent to you. You can see how I get around this limitation by including the email address in the text property of the mailOpts object:

text: `${req.body.name} (${req.body.email}) says: ${req.body.message}`

Here, I’m using body-parser to grab the fields name, email, and message from the incoming POST request in order to fill the body text of the email that will be sent. See Nodemailer's message configuration documentation for all the values you can set in your mail object.

The smtpTrans.sendMail() function uses the mailOpts object as its first argument. The second argument is a callback function that will use the res.render() function in Express to show a success or failure page (which is beyond the scope of this article).

Setup the HTML form

Now that we have our back-end route written in our Node/Express server, we are ready for the front-end HTML. Here is an extremely basic form you can add structure or styling to:

<form action="/contact" id="contact-form" method="post" role="form">
  <fieldset>
    <label for="name">Name</label>
    <input
      id="name"
      name="name"
      type="text"
      placeholder="Your name"
      required
    />
    <label for="email">Email</label>
    <input
      id="email"
      name="email"
      type="text"
      placeholder="Your email"
      required
    />
    <label for="message">Message</label>
    <textarea
      id="message"
      name="message"
      placeholder="Enter your message here"
      rows="3"
      required
    </textarea>
    <button type="submit">Submit</button>
  </fieldset>
</form>

It is vital that the form’s action attribute points to the same route location in server.js that we specified earlier (action="/contact" here matches with app.post('/contact' on our server). The form’s method is post to comply with RESTful conventions.

The name fields that you specify for the input and textarea elements correspond to how we access those values in our request body back on the server (req.body.name, req.body.email, and req.body.message).

The role="form" attribute is to assist with accessibility for users with screen-readers or similar devices.

Conclusion and limitations

This method of contact form creation is not very robust for deployment on high traffic sites. Gmail limits the number of emails you can send in a day, and busy sites will definitely attract a lot of spam, phishing, and injection attempts with this kind of setup. We also have not touched on pooled SMTP with Nodemailer or other considerations that a high-volume environment would require.

For me, this contact form design solved the basic problem of how to let people contact me without showing them my email address while I had it in production. It should be sufficient for the average portfolio site or small business marketing site.

Stretch goal: Add a verification requirement to your form so a human can fill it out but a spambot would get stuck. You can track down how I did this in my express-portfolio GitHub repo.

I recommend Nodemailer's documentation for help with writing your server code. Their docs are excellent.

I would love to hear your feedback! Look me up on LinkedIn to get in touch.

- Tyler Krys