Setting up a Serverless Contact form in React — using Nodemailer and Expressby@gmrsagar
29,396 reads

Setting up a Serverless Contact form in React — using Nodemailer and Express

January 31st 2020
7 min
by @gmrsagar 29,396 reads
tldt arrow
Read on Terminal Reader

Too Long; Didn't Read

Using Nodemailer and Express, we will set up our own contact form in React. This post will also walk us through deploying it serverless with Zeit Now. By the end of this tutorial, you will have a contact form on your website that will bring the message straight to your inbox. The form itself has an onSubmit function which handles our API calls. We will look at a component that returns an HTML form. We’re using Bootstrap classes for style in the code below, so they are optional.

People Mentioned

Mention Thumbnail

Company Mentioned

Mention Thumbnail
featured image - Setting up a Serverless Contact form in React — using Nodemailer and Express
Sagar Ghimire HackerNoon profile picture


Sagar Ghimire

About @gmrsagar
react to story with heart

Having a contact form always works better than just displaying an email address on our website. A contact form gives our visitors an easy way to get in touch with us.

In this post, we will go through a simple way to set up our own in React using Nodemailer and Express API. This post will also walk us through deploying it serverless with Zeit Now.

What this app will do

By the end of this tutorial, you will have a contact form on your website that will bring the message straight to your inbox.


GIFs from Giphy

Tools this app will use

  • GitHub — (for hosting code. It is also required to deploy with Zeit later)
  • Npm — (for using JS packages like create-react-app)
  • Node JS and Express JS (as our API will be built in Express)
  • React JS (create-react-app for bootstrapping a standard React application and setting up our form)
  • Axios (for submission of form data to our remote API)
  • Zeit Now (to deploy our app serverless)


1. Getting things ready

GitHub repos — We start by creating two GitHub repos, one to host our React form and another for Node API. We could also do it in a single repo, but we are using two for better maintainability.

Node & npm — Download the latest version of node.js from the link. In this post we are using version 11.7.0. Node comes with npm. To make sure node and npm are installed, check their versions using the following commands on the terminal:

// For node version
node -v
//For npm version
npm -v

React — We’re using create-react-app for our React application where we will build our form. Install create-react-app globally and generate our React app using the following commands:

//Install create-react-app globally
npm install create-react-app -g
//Generate a new react app called my-app
create-react-app my-app && cd my-app && npm start

2. The Form

Let’s get started with setting up a component that returns an HTML form. We’re using Bootstrap classes for style in the code below, so they are optional You can use your own CSS classes as well.

<form className="contact-form" onSubmit={ (e) => this.formSubmit(e)}>
  <label class="message" htmlFor="message-input">Your Message</label>
  <textarea onChange={e => this.setState({ message:})} name="message" class="message-input" type="text" placeholder="Please write your message here" value={this.state.message} required/>

  <label class="message-name" htmlFor="message-name">Your Name</label>
  <input onChange={e => this.setState({ name:})} name="name" class="message-name" type="text" placeholder="Your Name" value={}/>

  <label class="message-email" htmlFor="message-email">Your Email</label>
  <input onChange={(e) => this.setState({ email:})} name="email" class="message-email" type="email" placeholder="" required value={} />

  <div className="button--container">
      <button type="submit" className="button button-primary">{ this.state.buttonText }</button>

Here, each input has an onChange handler corresponding to a specific variable in our component’s state. So, when the input changes, the state gets updated as well. The form itself has an onSubmit handler that calls the formSubmit function which handles our API calls.

We will have a look at the function, but before that, let’s install axios (which we will be using in the function) to make HTTP request to the API.

//Install axios
npm install axios --save

Setting up our component src/App.js:

import React, { Component } from 'react';
import axios from 'axios';

class Contact extends Component {

    state = {
        name: '',
        message: '',
        email: '',
        sent: false,
        buttonText: 'Send Message'

    render() {
           //Our form goes here

export default Contact;

Now, we add the functions to handle form submission in our component.

formSubmit = (e) => {

      buttonText: '...sending'

  let data = {
      message: this.state.message
  }'API_URI', data)
  .then( res => {
      this.setState({ sent: true }, this.resetForm())
  .catch( () => {
    console.log('Message not sent')

The preventDefault() function (on line 2), as the name suggests, prevents the default action of the form, which would’ve caused a page reload. While the message is being sent, the button text changes to “…Sending”, and axios makes the API call.

Note: “API_URI”(line 14) is the uri of the endpoint of our API which will be discussed in the deployment section of this post(Step 4).

If everything goes right, the resetForm is called, which we haven’t defined yet. So let’s define the reset function that will reset our form fields and update our button label:

resetForm = () => {
        name: '',
        message: '',
        email: '',
        buttonText: 'Message Sent'

3. API

Now, we need something to transport our form inputs to the API, so we set up the nodemailer, config file and route. But before that, we need to setup Express.js in our Node API repo:

npm init
//Install express and other dependencies
npm install express body-parser nodemailer cors --save

Now we need to make a small change in our package.json file. Find “scripts” and add the following “start” line to it:

"scripts": {
  "start": "node ."

Now, in our directory we create an index.js file with the following code:

const express = require('express');
const bodyParser = require('body-parser');
const nodemailer = require('nodemailer');
const cors = require('cors');

const app = express();

const port = 4444;

app.use(bodyParser.urlencoded({ extended: true }));


app.listen(port, () => {
  console.log('We are live on port 4444');

app.get('/', (req, res) => {
  res.send('Welcome to my api');
})'/api/v1', (req,res) => {
  var data = req.body;

var smtpTransport = nodemailer.createTransport({
  service: 'Gmail',
  port: 465,
  auth: {
    user: 'USERNAME',
    pass: 'PASSWORD'

var mailOptions = {
  subject: 'ENTER_YOUR_SUBJECT',
  html: `<p>${}</p>

(error, response) => {
  if(error) {
  }else {


Important Note: Since we are working on the development version, the credentials will be exposed on GitHub, so make sure your repo is private or use a test email address for development version. Later in production, you can make use of the Zeit environment variables to store credentials safely.

We have installed body-parser to process the form data, and CORS to allow cross-origin requests.

We have setup a Nodemailer SMTP Transport and our route that will receive the data from our React form and send an email to the destination email address that we specify.

4. Deploy

Finally! with all that done it’s time to deploy our app. We’re using Zeit Now for deployment, you can choose any other service that you like.


(GIF from Giphy)

Start by creating an account on Zeit. Login and head over to “Now” from the top navigation. Connect your GitHub account and add the two repos that we just created. Now, we go back to our code and add an empty now.json file in both the repos.

It’s now time to push our codes to the respective github repos.

Now we create a new branch in both repos and work with those because we will need to create Pull Request for Zeit now to run deployment.

//create and change to new branch
git checkout -b new-branch

Now we add some content to the now.json files.
For our React repo we will use the following content on our now.json:

    "version": 2,
    "name": "create-react-app",
    "builds": [
        { "src": "package.json", "use": "@now/static-build" }
    "routes": [
        {"src": "^/static/(.*)", "dest": "/static/$1"},
        {"src": "^/favicon.ico", "dest": "/favicon.ico"},
        {"src": "^/asset-manifest.json", "dest": "/asset-manifest.json"},
        {"src": "^/manifest.json", "dest": "/manifest.json"},
        {"src": "^/service-worker.js", "headers": {"cache-control": "s-maxage=0"}, "dest": "/service-worker.js"},
        {"src": "^/precache-manifest.(.*)", "dest": "/precache-manifest.$1"},
        {"src": "^/(.*)", "dest": "/index.html"}

For our Node repo, we will use the following content on now.json:

  "version": 2,
  "name": "nodejs-express",
  "builds": [
    { "src": "index.js", "use": "@now/node-server"}
  "routes": [
    { "src": "/(.*)", "dest": "app.js"}

Note: More examples for Zeit Now configurations can be found here.

Finally, we make a small change in our package.json in our React repo. 
Find “scripts” and add the “now-build” line to it:

    "scripts": {
        "now-build": "react-scripts build && mv build dist"

Now, we push the codes to their respective Github repos(new-branch) and then create a Pull Request in each. Zeit Now will immediately deploy the app recognizing the now.json files.

You can view the app URL by clicking “Show all checks” and then “details” in the Pull Request summary. Lastly, be sure to copy the link and replace API_URI in the App.js file in React app. Now our form should successfully deliver emails.

Thanks for reading!
Happy Coding!

Illustration by Akriti Bhusal 🥳


. . . comments & more!
Hackernoon hq - po box 2206, edwards, colorado 81632, usa