Creating contact forms with nodemailer and React

Creating a contact form is a very common requested feature, almost every site has one. Creating the HTML form is easy, however there must be some back-end code to transfer those messages. I will show you how to create simple form and wire that up to send contact form submission to your Gmail account.

Rendering contact form with React

For the sake of clarity there won't be any styling included and the components will be plain old HTML form and input tags. That simple component will do the job:

const ContactForm = () =>
<form method="POST">
    <label htmlFor="name">Name</label>
    <input type="text" name="name" />

    <label htmlFor="email">Email</label>
    <input type="email" name="email" />

    <label htmlFor="message">Message </label>
    <textarea name="message" rows="3"></textarea>

    <input type="submit" />
</form>

The contact form will be extended with event handling, validation and submission after the server-side setup is done.

Configuring Gmail account with OAuth2

Many of you use Gmail on daily basis and receive tons of emails. You might want to use dedicated email provider for the contact form but most likely your private account will be enough. I will show you how to configure your Gmail account so the contact form submissions will land right in your inbox. If you would like to use SMTP or use plain user/pass authentication head up for the official example.

Registering application on Google Clould Platform

Navigate to Google Cloud Platform Console and sign in with the account you wish to receive emails.

Create a new project

Create a new project and assign it a name, for example 'Contact form'. Save the generated project-id.

Enable Gmail API

Navigate to Manage APIs section using side navigation and find Gmail API . Enable it.

Get API keys

Switch to Manage credentials section and save the generated file. Copy it to your project.

Accuire access token

You can accuire access token from the server side, however it is a bit complicated. For the simple purposes you can use Google OAuth Playground.

Select Gmail API v1 and add it to the scope and proceed to authorization. You will be prompted with the permissions displayed. You might be denied authorization with a message of "unreviewed app", then just simply:

Join Allow Risky Access Permissions By Unreviewed Apps Google group

In order to enable Gmail access by the app you must get through out verification process by Google. For the purposed described here we can leave that and just join Allow Risky Access Permissions By Unreviewed Apps group and the access will be granted. It is really funny how the security is implemented by Google.

After the authorization is complete Exchange authorization code for tokens and save the access and refresh tokens. You are all set!

Using nodemailer

Configuring the transporter

With the access token and API keys created before, create a configuration file:

import source from './source.json'

const user = USER
const refreshToken = REFRESH_TOKEN
const accessToken = ACCESS_TOKEN

export default {
    user,
    clientId: source.web.client_id,
    clientSecret: source.web.client_secret,
    refreshToken,
    accessToken
}

Now you can create and authorize nodemailer and export a simpler interface for the consumption.

import nodemailer from 'nodemailer'
import config from './config'

const transporter = nodemailer.createTransport({
    service: 'Gmail',
    auth: {
        type: 'OAuth2',
        ...config
    }
});

As for the most of the node.js APIs and libraries, nodemailer uses callback-style programming, but if you prefer Promises and/or async/await you can simply wrap the function:

const sendMail = message => {
    return new Promise((resolve, reject) => {
        transporter.sendMail(message, (error, info) => {
            if (error) {
                reject(error)
                return
            }
            resolve(info)
        });
    })
}

export default sendMail

Handling form submission

On the express.js side you can now use the sendMail function created before. Data from the form comes in the body, but first, you must configure body-parser to get the form-url-encoded content.

import bodyParser from 'body-parser'

app.use(bodyParser.urlencoded());

You can now send emails when handling POST requests on the contact form submission route. It's very straightforward to send emails with the configured mailer, however the user should be notified about the result. There are few options out there: redirect to the success or error page, return a status code or combine both.

The third option is the likely preferred one as it allows to gracefully degrade the experience when something goes wrong, for example the user has JavaScript disabled or an error occured in the React app (error isolation?).

import sendEmail from 'mailer'

app.post('/', async (req, res) => {
    try {
        const { name, email, message } = req.body
        const message = {
            from: `${name} <${email}>`
            text: message
        }
        await sendEmail(message)

        res.redirect('/example#contact-success');
    } catch (error) {
        res.redirect('/example#contact-error')
    }
})

Demo and source code

After all that hussle, you can try sending me an email by using following code:

<ContactFormExample />

Working example and source code of this article is available on GitHub. Just download or clone this repository, npm install and you can fiddle with sending emails.