Build and Deploy a Serverless ReactJS Contact Form with Nodemailer and Netlify Functions
Tags: react, node, serverless, netlify
Published: 2020-09-07 13:09:47

ReactJS is a client side JavaScript framework.  As such, while you can build good-looking contact forms with loads of client-facing functionality, you need to look elsewhere to do something that requires backend functionality such as send an email of add an entry to a database.  This is the challenge I will be addressing in this post - how do you build and deploy a ReactJS contact form that will send an email when submitted.

Our toolbox will consist of:
 - ReactJS (obviously)
 - Axios (to post data)
 - Nodemailer (a Node.js package used to send emails via SMTP)
 - Netlify (for deploying)

We are going to capture data from our frontend form and post it to a backend url.  We will build a Netlify function which will serve as our backend, take the form data we post and use [Nodemailer](https://nodemailer.com/about/) to email the data to the recipient. 

It really is as easy as it sounds.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/varjcxa83cnf20riykj2.png)

Let's get started...

## Front End
First we'll build the front end using ReactJS.  To set things up we'll run <code>npx create-react-app contact-form</code> in our terminal window.  This will provide the standard ReactJS app which we'll modify.  And then we wait...

...once our react app is installed we run `npm start` to run the app in the browser. We open `src/App.js` and remove everything between the `<header>` tags so our file looks like this:

```jsx
import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
    </div>
  );
}

export default App;
```

While you're at it, get rid of `import logo from './logo.svg'`.  Ahh, now we have a blank canvas 😌.

Now, create a new file in the `src` directory.  This will be our contact form module.  I'll call mine `contact-form.js`, you can call yours whatever you want.  The basic structure of a React module is:

```jsx
import React from 'react'

export default function FunctionName() {
    return (
        ...
    )
}

```

So we can start by building the structure of our contact form.  I'm using [material-us](https://material-ui.com/) but again, you can use the CSS framework of you choice.  All that matters is that you have a form:

```jsx
import React from 'react'
import TextField from "@material-ui/core/TextField";
import Button from "@material-ui/core/Button"
import FormControl from "@material-ui/core/FormControl"

export default function Form() {

    return (
        <>
            
            <FormControl fullWidth={true}>
                <TextField required label="Full name" variant="filled" id="full-name" name="name" className="form-field" />
            </FormControl>
            <FormControl fullWidth={true}>
                <TextField required label="Email" id="email" name="email" variant="filled" className="form-field" onChange />
            </FormControl>
            <FormControl fullWidth={true}>
                <TextField required label="Message" variant="filled" name="message" multiline={true} rows="10" />
            </FormControl>
            <FormControl>
                <div style={{padding: 20}}>
                    <Grid container spacing={2}>
                            <div className="form-submit">
                                <Button variant="contained" color="primary">Submit</Button>
                            </div>
                        </Grid>
                    </Grid>
                </div>
            </FormControl>
    )
}

```

Now we can import the contact form in `App.js`.  We modify `App.js` as follows:

```jsx
import React from 'react';
import logo from './logo.svg';
import Form from './contactform'
import './App.css';

function App() {
  return (
    <div className="App">
      <Form />
    </div>
  );
}

export default App;
```

### Capture Form Data

There are a few additions we need to make.  First, we need to capture the form data.  And what better way to do this than by using [react hooks](https://reactjs.org/docs/hooks-intro.html) - specifically `useState` which we will use to track and update the 'state' of our data in realtime.  Modify the first line in `contactform.js` to include the `useState` hook:

```javascript
import React, { useState } from 'react'
```

Next, we instantiate `useState` variable.  The variable is a two-item `array` with the first item being the state we are tracking, and the second item a function used to update that state:

```jsx
export default function Form() {
    const [data, setData] = useState()
    
    return (
        ...
    )
}
```

Because we need to capture more than one field from the form, we'll setup `data` as an `object`:
```javascript
export default function Form() {
    const [data, setData] = useState({name: '', email: '', message: '', sent: false, buttonText: 'Submit', err: ''})
    
    return (
        ...
    )
}
```

As you can see, we do this by simply setting the initial value of useState in [object notation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects). We also setup a few utility items to track the status of our request and provide feedback to the user, namely `sent`, `buttonText` and `err`.  More on these later.

Now we need a way to update our `data` object.  Easy peasy - we setup a function that tracks changes to our form fields:

```jsx
...

const [data, setData] = useState({name: '', email: '', message: '', sent: false, buttonText: 'Submit', err: ''})

const handleChange = (e) => {
    const {name, value} = e.target
        setData({
            ...data,
            [name]: value
    })
}

...
```

As its name suggests this function will be called whenever a user changes one of the form fields (i.e. by filling it in).  The function uses object destructing to grab the `name` and `value` attributes of the form field being changed and updates the corresponding value in the `data` object.

The last thing we need to do is update the `onChange` and `value` attributes of our form fields to call this function as the user types:

```jsx
<FormControl fullWidth={true}>
    <TextField required label="Full name" variant="filled" id="full-name" name="name" className="form-field" value={data.name} onChange={handleChange} />
</FormControl>
<FormControl fullWidth={true}>
     <TextField required label="Email" id="email" name="email" variant="filled" className="form-field" value={data.email} onChange={handleChange} />
</FormControl>
<FormControl fullWidth={true}>
     <TextField required label="Message" variant="filled" name="message" multiline={true} rows="10" value={data.message} onChange={handleChange} />
</FormControl>
<FormControl>
    <div className="form-submit">
        <Button variant="contained" color="primary">Submit</Button>
    </div>
</FormControl>
```

### Handle Form Submissions
We need to setup a function that handles form submissions and we'll call it

```jsx
const formSubmit = (e) => {
    e.preventDefault()
} 
```

We use the `preventDefault` function to stop the form redirecting the user to the backend URL which is its default behaviour.

Remember way back when, when I said we need to `post` the data to our backend URL?  Well that's where [Axios](https://www.npmjs.com/package/axios) comes in - it's a promise-based http client and will serve our needs perfectly.  Grab it by running `npm i axios` and once it's installed we can finish our submit function:

```jsx
const formSubmit = (e) => {
        e.preventDefault();

        setData({
            ...data,
            buttonText: 'Sending...'
        })

        axios.post('/api/sendmail', data)
        .then(res => {
            if(res.data.result !=='success') {
                setData({
                    ...data,
                    buttonText: 'Failed to send',
                    sent: false,
                    err: 'fail'
                })
                setTimeout(() => {
                    resetForm()
                }, 6000)
            } else {
                setData({
                    ...data,
                    sent: true,
                    buttonText: 'Sent',
                    err: 'success'
                })
                setTimeout(() => {
                    resetForm();
                }, 6000)
            }
        }).catch( (err) => {
            //console.log(err.response.status)
            setData({
                ...data,
                buttonText: 'Failed to send',
                err: 'fail'
            })
        })
    }
```

Let's go through what this function does.  After preventing the default behaviour of the form, the form sets the `buttonText` item of the `data` object to 'Sending...'.  We will use this to change the text on the submit button and give the user some feedback.

Next, the function perfoms and `axios.post` request to the url `api/sendmail` which will call our Netlify function when we buid that.  If the response is anything **but** 'success' the button text will be changed to 'Failed to send' and our utility item `err` will be set to 'fail' for use later.  The form then resets after 6 seconds with the `setTimeout` function.  

If the response is 'success' then the button text is changed to 'Sent' and `err` item changed to 'success'.  We then deal with any request-related errors in the same way within the `catch` clause.

You'll notice that we reference a `resetForm` function.  And here it is:

```jsx
    const resetForm = () => {
        setData({
            name: '',
            email: '',
            message: '',
            sent: false,
            buttonText: 'Submit',
            err: ''
        });
    }
```

This function sets the `data` object back to its original state.  

We then just need to change the `onClick` and value attributes of our button to call the `handleSubmit` function and update the button text accordingly:

```jsx
<Button variant="contained" color="primary" onClick={formSubmit}>{data.buttonText}</Button>

``` 

### Netlify Functions
[Netlify functions](https://www.netlify.com/products/functions/) allow you to write APIs that give your apps server-side functionality.  In our case we are going to write a function that will take our `data` object as a `post` request and use `nodemailer` to send an email to a recipient.

The first thing I would suggest is to install the Netlify CLI by running `npm install netlify-cli -g`.  This wil help us to test our form.  Then we create a directory called `functions` in our project root (you don't have to call it 'functions').  In `functions` folder, create a file called `sendmail.js`.  Notice something?  Our `axios.post` requests posts to `api/sendmail` - this is important the post location and the function filename need to be the same.  

By this point, Netlify CLI should have installed, so we grab a copy of nodemailer which is a free Node.js module that, in their words, allows 'easy as cake email sending'.  Everyone loves cake.  Run `npm install nodemailer`.

While that's installing we head into our `sendmail.js` file and add this code:

```javascript
const nodemailer = require('nodemailer');

exports.handler = function(event, context, callback) {

    let data = JSON.parse(event.body)

    let transporter = nodemailer.createTransport({
        host:[YOUR SMTP SERVER],
        port:[YOUR SMTP SERVER PORT],
        auth:{
         user:[YOUR SMTP SERVER USERNAME],
         pass: [YOUR SMTP SERVER PASSWORD]
    }
    });

    transporter.sendMail({
        from: [YOUR SMTP SERVER EMAIL ADDRESS],
        to: [RECIPIENT EMAIL ADDRESS],
        subject: `Sending with React, Nodemailer and Netlify`,
        html: `
            <h3>Email from ${data.name} ${data.email}<h3>
            <p>${data.message}<p>
            `
    }, function(error, info) {
    	if (error) {
    		callback(error);
    	} else {
    		callback(null, {
		    statusCode: 200,
		    body: JSON.stringify({
                   'result': 'success'
                })
	    });
    	}
    });
}
```

What does this function do, I hear you ask?  Netlify functions are all setup in the same way and are [documented extensively](https://docs.netlify.com/functions/build-with-javascript/#format).  In short they export a `handler` method and take `event`, `context` and `callback` parameters.  In our case we use the `event` and `callback` parameters which are the equivelant of `request` and `response`.

First the function parses the request `data` object.  Next we declare and setup a `transporter` variable which holds data related to the SMTP transport we are using.  Nodemailer requires  the SMTP server, port and authentication information of your chosen SMTP transport.  I used [Zoho mail](https://www.zoho.com/mail/) which is free, but you can use any provider (Hotmail, Outlook, ...).  You can use Gmail which seems a popular choice but there are [documented issues with using Gmail](https://nodemailer.com/usage/using-gmail/) so you may want to use another provider.  

You can read more about [nodemailer SMTP transport here](https://nodemailer.com/smtp/).  There is a list of [well-known SMTP services that work with nodemailer here](https://nodemailer.com/smtp/well-known/).

Back to the function.  Once the `transporter` variable is setup we use `transporter.sendMail(data[, callback])` to configure our  message and send the email.

### Setting Redirects
We need to do a few final bits to get this up and running.  First, we need to create a `netlify.toml` file in our project root.  This file let's Netlify know what the build configuration is and where any functions are located.  In our `netlify.toml` file we add two crucial pieces of configuration:

```
[build]
    functions = "functions"
[[redirects]]
    from = "/api/*"
    to = "/.netlify/functions/:splat"
    status = 200
```

The first is a build command that tells Netlify that our functions are in the `functions` directory.  Simple.

The second is a redirect that tells Netlify to redirect anything posted to `/api/*` should be redirected to our function in the `/.netlify/functions/` directory.  The `:splat` keyword tells Netlify for match anything that follows the asterisk (*), so anything posted to `/api/sendmail/` would be redirected to `/.netlify/functions/sendmail`, and look `sendmail` just happens to be the name of our function file.  So our posted data will end up in our function as expected.  [You can read more about Netlify redirects here](https://docs.netlify.com/routing/redirects/redirect-options/#splats)

### Test Deploy
Because we have installed Netlify CLI, it's easy to test our form by running `netlify dev` in our terminal.  This will run a local copy of our contact form.

### Conclusion
I've added some basic validation to the form as well as `react-google-captcha`.  You can check out all the code in [this repo](https://github.com/dialdt/react-contact-form).  For the Netlify function, I modified the code found in [this repo](https://github.com/kicholen).  There are a lot of Netlify functions example code snippets here too.

Cover photo credit: <span>Photo by <a href="https://unsplash.com/@brett_jordan?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Brett Jordan</a> on <a href="https://unsplash.com/s/photos/email?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></span>