Password Reset Emails In Your React App Made Easy with Nodemailer
Tags: node, react, javascript
Published: 2022-10-29 23:00:47

---
title: Password Reset Emails In Your React App Made Easy with Nodemailer
published: true
date: 2018-11-22 00:00:00 UTC
tags: nodejs,react,javascript
canonical_url: https://www.paigeniedringhaus.com/blog/password-reset-emails-in-your-react-app-made-easy-with-nodemailer
---

[![Logos of React, Node.js, and Nodemailer](https://www.paigeniedringhaus.com/static/a7064890108db3d198b09d01e8a2a004/1e043/nodemailer-hero.png "Logos of React, Node.js, and Nodemailer")](/static/a7064890108db3d198b09d01e8a2a004/2cefc/nodemailer-hero.png)

## Password Resets in a MERN Application

Before I actually attempted to build in an email-based password reset for my MERN app, I thought it would be tougher to do. From everything I’d heard before, sending emails in a JavaScript application was painful, but I still wanted to attempt it.

For a few months, to hone my full stack JavaScript skills, I’ve been slowly building and adding on to a [user registration service](https://github.com/paigen11/mysql-registration-passport).

First, I built it with a React frontend, an Express / Node.js backend and a Docker-powered MySQL database. I used a `docker-compose.yml` to start the whole app with one command (if you’d like to read more about my use of Docker for development, you can see [**this blog post**](https://www.paigeniedringhaus.com/blog/using-docker-docker-compose-to-improve-your-full-stack-app-development)).

After I’d gotten that working, I added in authorization to the app using Passport.js and JSON Web Tokens (JWTs). You can read about the fun (pain) of that [**here**](https://www.paigeniedringhaus.com/blog/implementing-json-web-tokens-passport-js-in-a-react-app), if you’re curious. And it took me a while — I ran into a bunch of road blocks and obstacles that stalled me out multiple times. But grit and my inability to let go of a problem once it’s taken root in my brain prevailed, and I figured it out and moved on.

When I decided to tackle sending password reset links via email (just like real sites when users, myself included, inevitably forget their password), I figured I was in for more pain. It just couldn’t be too easy, no matter that practically every site out there has this very functionality. But I was wrong. And I’m so glad I was.

**Today, I'll show you how to set up your own password reset system with help from Nodemailer.**

---

## Nodemailer: the magic bullet

[![Nodemailer logo](https://www.paigeniedringhaus.com/static/b637191000560597aaff4e696545c70f/8aea6/nodemailer-logo.png "Nodemailer logo")](/static/b637191000560597aaff4e696545c70f/8aea6/nodemailer-logo.png)

Once I started googling around looking for solutions to my password reset feature, I came across a number of articles recommending [Nodemailer](https://nodemailer.com/about/).

When I visited the website, the first lines I read were:

> Nodemailer is a module for Node.js applications to allow easy as cake email sending. The project got started back in 2010 when there was no sane option to send email messages, today it is the solution most Node.js users turn to by default. — Nodemailer

And you know what? It wasn’t kidding. Easy as cake isn’t too far wrong.

Of course, before I got started, I did a little more digging to make sure I was placing my faith in a decent piece of technology, and what I saw on [npm](https://www.npmjs.com/package/nodemailer) and [GitHub](https://github.com/nodemailer/nodemailer) put my mind at ease.

Nodemailer has:

- Over 615,000 weekly downloads from NPM,
- Over 10,000 stars on Github,
- 206 releases to date,
- Over 2,500 dependent packages,
- And it’s been around in some form or fashion since 2010.

Ok, that seemed good enough for me to give it a shot in my own project.

### Implementation of Nodemailer in my code (frontend and backend)

I didn’t need anything fancy for my password reset, just:

- A way to send an email to a user’s address.
- That email would contain a link that would redirect them to a protected page on my site where they could reset their password.
- Then they could log in using their new password.
- I also wanted the password reset link to expire after a certain time period for better security.

Here’s how I did it.

### Frontend code (client folder): send reset email

I’ll start with the React code first, because I had to have a page where users could enter their email addresses and fire off the email with the reset link.

[**`ForgotPassword.js`**](https://github.com/paigen11/mysql-registration-passport/blob/master/client/src/containers/ForgotPassword.js)

```javascript
/* eslint-disable no-console */
import React, { Component } from 'react';
import TextField from '@material-ui/core/TextField';
import axios from 'axios';
import {
  LinkButtons,
  SubmitButtons,
  registerButton,
  homeButton,
  forgotButton,
  inputStyle,
  HeaderBar,
} from '../components';

const title = {
  pageTitle: 'Forgot Password Screen',
};

class ForgotPassword extends Component {
  constructor() {
    super();

    this.state = {
      email: '',
      showError: false,
      messageFromServer: '',
      showNullError: false,
    };
  }

  handleChange = name => (event) => {
    this.setState({
      [name]: event.target.value,
    });
  };

  sendEmail = async (e) => {
    e.preventDefault();
    const { email } = this.state;
    if (email === '') {
      this.setState({
        showError: false,
        messageFromServer: '',
        showNullError: true,
      });
    } else {
      try {
        const response = await axios.post(
          'http://localhost:3003/forgotPassword',
          {
            email,
          },
        );
        if (response.data === 'recovery email sent') {
          this.setState({
            showError: false,
            messageFromServer: 'recovery email sent',
            showNullError: false,
          });
        }
      } catch (error) {
        console.error(error.response.data);
        if (error.response.data === 'email not in db') {
          this.setState({
            showError: true,
            messageFromServer: '',
            showNullError: false,
          });
        }
      }
    }
  };

  render() {
    const { email, messageFromServer, showNullError, showError } = this.state;

    return (
      <div>
        <HeaderBar title={title} />
        <form className="profile-form" onSubmit={this.sendEmail}>
          <TextField
            style={inputStyle}
            id="email"
            label="email"
            value={email}
            onChange={this.handleChange('email')}
            placeholder="Email Address"
          />
          <SubmitButtons
            buttonStyle={forgotButton}
            buttonText="Send Password Reset Email"
          />
        </form>
        {showNullError && (
          <div>
            <p>The email address cannot be null.</p>
          </div>
        )}
        {showError && (
          <div>
            <p>
              That email address isn't recognized. Please try again or
              register for a new account.
            </p>
            <LinkButtons
              buttonText="Register"
              buttonStyle={registerButton}
              link="/register"
            />
          </div>
        )}
        {messageFromServer === 'recovery email sent' && (
          <div>
            <h3>Password Reset Email Successfully Sent!</h3>
          </div>
        )}
        <LinkButtons buttonText="Go Home" buttonStyle={homeButton} link="/" />
      </div>
    );
  }
}

export default ForgotPassword;
```

Ok, I know this is a big code snippet but I’ll break it down.

> If you want to copy/paste the actual code, you can see the whole repo [**here**](https://github.com/paigen11/mysql-registration-passport).

What you should really focus on here is the `sendEmail() `function and the `render()` method of the component. The rest is just setting initial state and variables, and styling of buttons and elements.

**The render() method**

```javascript
render() {
    const { email, messageFromServer, showNullError, showError } = this.state;

    return (
      <div>
        <HeaderBar title={title} />
        <form className="profile-form" onSubmit={this.sendEmail}>
          <TextField
            style={inputStyle}
            id="email"
            label="email"
            value={email}
            onChange={this.handleChange('email')}
            placeholder="Email Address"
          />
          <SubmitButtons
            buttonStyle={forgotButton}
            buttonText="Send Password Reset Email"
          />
        </form>
        {showNullError && (
          <div>
            <p>The email address cannot be null.</p>
          </div>
        )}
        {showError && (
          <div>
            <p>
              That email address isn&apos;t recognized. Please try again or
              register for a new account.
            </p>
            <LinkButtons
              buttonText="Register"
              buttonStyle={registerButton}
              link="/register"
            />
          </div>
        )}
        {messageFromServer === 'recovery email sent' && (
          <div>
            <h3>Password Reset Email Successfully Sent!</h3>
          </div>
        )}
        <LinkButtons buttonText="Go Home" buttonStyle={homeButton} link="/" />
      </div>
    );
  }
```

Notice inside the `render()` method, that I have a simple input box for the user to enter his/her email address, and a submit button which triggers the `this.sendEmail()` function when clicked. Beyond that, I have a little error and success handling built in based on if the user hasn’t entered an email, if the server responds back the email was successfully sent or it wasn’t a recognized address.

**The sendEmail() function**

```javascript
  sendEmail = async (e) => {
    e.preventDefault();
    const { email } = this.state;
    if (email === '') {
      this.setState({
        showError: false,
        messageFromServer: '',
        showNullError: true,
      });
    } else {
      try {
        const response = await axios.post(
          'http://localhost:3003/forgotPassword',
          {
            email,
          },
        );
        if (response.data === 'recovery email sent') {
          this.setState({
            showError: false,
            messageFromServer: 'recovery email sent',
            showNullError: false,
          });
        }
      } catch (error) {
        console.error(error.response.data);
        if (error.response.data === 'email not in db') {
          this.setState({
            showError: true,
            messageFromServer: '',
            showNullError: false,
          });
        }
      }
    }
  }
```

For all of my HTTP requests, I’m using the [Axios library](https://www.npmjs.com/package/axios), which just makes it really easy to make AJAX calls to the server, even easier than the built in fetch() web API, in my opinion.

When the user enters his/her email, I make a `POST` request to the server, and wait for a response. If the email address isn’t found, I can tell the user they entered it wrong or if they’re new, they can go to the register page and create an account, and if the address does match one in my database, they’ll get back a success message saying the password reset link has been sent to their email address.

Let’s move on to the backend code now.

### Backend code (API Folder): send reset email

[**`forgotPassword.js`**](https://github.com/paigen11/mysql-registration-passport/blob/master/api/routes/forgotPassword.js)

```javascript
/* eslint-disable max-len */
/* eslint-disable no-console */
import crypto from 'crypto';
import User from '../sequelize';

require('dotenv').config();

const nodemailer = require('nodemailer');

module.exports = (app) => {
  app.post('/forgotPassword', (req, res) => {
    if (req.body.email === '') {
      res.status(400).send('email required');
    }
    console.error(req.body.email);
    User.findOne({
      where: {
        email: req.body.email,
      },
    }).then((user) => {
      if (user === null) {
        console.error('email not in database');
        res.status(403).send('email not in db');
      } else {
        const token = crypto.randomBytes(20).toString('hex');
        user.update({
          resetPasswordToken: token,
          resetPasswordExpires: Date.now() + 3600000,
        });

        const transporter = nodemailer.createTransport({
          service: 'gmail',
          auth: {
            user: `${process.env.EMAIL_ADDRESS}`,
            pass: `${process.env.EMAIL_PASSWORD}`,
          },
        });

        const mailOptions = {
          from: 'mySqlDemoEmail@gmail.com',
          to: `${user.email}`,
          subject: 'Link To Reset Password',
          text:
            'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n'
            + 'Please click on the following link, or paste this into your browser to complete the process within one hour of receiving it:\n\n'
            + `http://localhost:3031/reset/${token}\n\n`
            + 'If you did not request this, please ignore this email and your password will remain unchanged.\n',
        };

        console.log('sending mail');

        transporter.sendMail(mailOptions, (err, response) => {
          if (err) {
            console.error('there was an error: ', err);
          } else {
            res.status(200).json('recovery email sent');
          }
        });
      }
    });
  });
};
```

The backend code is a little more involved. This is where Nodemailer comes into play.

When the user hits the `forgotPassword` route on the backend with the email address they entered, the first thing [Sequelize](https://www.paigeniedringhaus.com/blog/sequelize-like-mongoose-but-for-sql) (my SQL ORM) does is check if that email exists in my database. If it doesn’t the user gets notified they may have mistyped it, if it does exist, then a series of other events starts.

None of the following steps is very difficult, it’s just chaining them all together that was a little tricky, at first.

**1. Generate an expiring token for the user**

```javascript
 const token = crypto.randomBytes(20).toString('hex');
        user.update({
          resetPasswordToken: token,
          resetPasswordExpires: Date.now() + 3600000,
        });
```

The first step after confirming the email belongs to a user in the database, is generating a token that can be attached to the user’s account and setting a time limit for that token to be valid.

Node.js has a built in module called [Crypto](https://nodejs.org/api/crypto.html#crypto_crypto), which provides cryptographic functionality, which is a fancy way of saying, I can generate a unique hash token easily using the JavaScript `crypto.randomBytes(20).toString('hex');`. Then, I save that new token to my user’s profile in the database under the column name `resetPasswordToken`. I also set a timestamp for how long that token will be valid. I made mine valid for one hour after sending the link: `Date.now() + 3600000`.

**2. Create Nodemailer transport**

```javascript
 const transporter = nodemailer.createTransport({
          service: 'gmail',
          auth: {
            user: `${process.env.EMAIL_ADDRESS}`,
            pass: `${process.env.EMAIL_PASSWORD}`,
          },
        });
```

Next, I created the transporter which is actually the account sending the password reset email link.

I chose to use Gmail, because I use Gmail personally, and I created a new dummy account to send the emails. Since I don’t want to give out the credentials for that account to anyone, I put the credentials into a `.env` file that is included in my `.gitignore` so it never gets committed to GitHub or anywhere else.

The npm package [dotenv](https://www.npmjs.com/package/dotenv) is used to read the contents of the file and insert the email address and password for Nodemailer’s [createTransport()] function to pick up.

**3. Create mail options**

```javascript
 const mailOptions = {
          from: 'mySqlDemoEmail@gmail.com',
          to: `${user.email}`,
          subject: 'Link To Reset Password',
          text:
            'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n'
            + 'Please click on the following link, or paste this into your browser to complete the process within one hour of receiving it:\n\n'
            + `http://localhost:3031/reset/${token}\n\n`
            + 'If you did not request this, please ignore this email and your password will remain unchanged.\n',
        };
```

The third step is creating the email template or `mailOptions` as Nodemailer calls it that the user will see (and this is also where the verified email address they pass from the frontend input gets used).

There are whole third-party libraries for making great looking emails to go with the Nodemailer module, but I just wanted a bare bones email, so I made this one myself.

It contains the `from` email address (mySqlDemoEmail[at]gmail.com, for me), the user’s email goes in the `to` box, the `subject` line is something along the lines of reset password link, and the `text` is a simple string containing a little info and the website’s URL reset route including the token I created earlier, tacked on to the end. This will allow me to verify the user is who they say they are when they click the link and go to the site to reset their password.

**4. Send Email**

```javascript
        transporter.sendMail(mailOptions, (err, response) => {
          if (err) {
            console.error('there was an error: ', err);
          } else {
            res.status(200).json('recovery email sent');
          }
        });
```

The final step of this file actually putting together the pieces I created: the `transporter`, the `mailOptions`, the `token` and using Nodemailer’s `sendMail()` function. If it works, I’ll get back a 200 response, which I then use to trigger a success call to the client, and if it fails, I log out the error to see what went wrong.

### Enabling Gmail to send reset emails

There’s an extra gotcha to be aware of when setting up the transporter email that all the emails are sent from, at least, when using Gmail.

In order to be able to send emails from an account, 2-Step verification must be disabled, and a setting titled ‘Allow less secure apps’ must be toggled to on. See screenshot below. To do this, I went to my settings [here](https://myaccount.google.com/lesssecureapps), and turned it on.

Now, I could send reset emails with no problems. If you’re having trouble, check Nodemailer’s FAQs for more help.

[![Make Gmail less secure to practice sending reset emails](https://www.paigeniedringhaus.com/static/89536a04dbfcb64b2a66621911600fe7/1e043/gmail-less-secure.png "Make Gmail less secure to practice sending reset emails")](/static/89536a04dbfcb64b2a66621911600fe7/2cefc/gmail-less-secure.png)

_This is the screen you should see where you can turn ‘on’ less secure apps. Just one more reason to use some dummy email account instead of your actual Gmail account too._

### Frontend code: update password screen

Great, now users should be getting reset emails in their inbox that look something like this.

[![Password reset email](https://www.paigeniedringhaus.com/static/169b423e51e73dbe48b108462bc97b6c/1e043/password-reset-email.png "Password reset email")](/static/169b423e51e73dbe48b108462bc97b6c/2cefc/password-reset-email.png)

_It’s a simple email, but it does what I need it to do._

If you notice, the third line is a link to my website (running locally on port 3031), to another page called ‘Reset’, followed by the hashed token I generated back in step 1 with the Node.js `crypto` module.

When a user clicks this link, they’re directed to a new page in the application entitled ‘Password Reset Screen’, which can only be accessed with a valid token. If the token has expired or is otherwise invalid, the user will see an error screen with links to go home or attempt to send a new password reset email.

Here’s the React code for the reset screen.

[**`ResetPassword.js`**](https://github.com/paigen11/mysql-registration-passport/blob/master/client/src/containers/ResetPassword.js)

```javascript
/* eslint-disable no-console */
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import axios from 'axios';
import TextField from '@material-ui/core/TextField';

import {
  LinkButtons,
  updateButton,
  homeButton,
  loginButton,
  HeaderBar,
  forgotButton,
  inputStyle,
  SubmitButtons,
} from '../components';

const loading = {
  margin: '1em',
  fontSize: '24px',
};

const title = {
  pageTitle: 'Password Reset Screen',
};

export default class ResetPassword extends Component {
  constructor() {
    super();

    this.state = {
      username: '',
      password: '',
      updated: false,
      isLoading: true,
      error: false,
    };
  }

  async componentDidMount() {
    const {
      match: {
        params: { token },
      },
    } = this.props;
    try {
      const response = await axios.get('http://localhost:3003/reset', {
        params: {
          resetPasswordToken: token,
        },
      });
      if (response.data.message === 'password reset link a-ok') {
        this.setState({
          username: response.data.username,
          updated: false,
          isLoading: false,
          error: false,
        });
      }
    } catch (error) {
      console.log(error.response.data);
      this.setState({
        updated: false,
        isLoading: false,
        error: true,
      });
    }
  }

  handleChange = name => (event) => {
    this.setState({
      [name]: event.target.value,
    });
  };

  updatePassword = async (e) => {
    e.preventDefault();
    const { username, password } = this.state;
    const {
      match: {
        params: { token },
      },
    } = this.props;
    try {
      const response = await axios.put(
        'http://localhost:3003/updatePasswordViaEmail',
        {
          username,
          password,
          resetPasswordToken: token,
        },
      );
      if (response.data.message === 'password updated') {
        this.setState({
          updated: true,
          error: false,
        });
      } else {
        this.setState({
          updated: false,
          error: true,
        });
      }
    } catch (error) {
      console.log(error.response.data);
    }
  };

  render() {
    const {
 password, error, isLoading, updated 
} = this.state;

    if (error) {
      return (
        <div>
          <HeaderBar title={title} />
          <div style={loading}>
            <h4>Problem resetting password. Please send another reset link.</h4>
            <LinkButtons
              buttonText="Go Home"
              buttonStyle={homeButton}
              link="/"
            />
            <LinkButtons
              buttonStyle={forgotButton}
              buttonText="Forgot Password?"
              link="/forgotPassword"
            />
          </div>
        </div>
      );
    }

    if (isLoading) {
      return (
        <div>
          <HeaderBar title={title} />
          <div style={loading}>Loading User Data...</div>
        </div>
      );
    }

    return (
      <div>
        <HeaderBar title={title} />
        <form className="password-form" onSubmit={this.updatePassword}>
          <TextField
            style={inputStyle}
            id="password"
            label="password"
            onChange={this.handleChange('password')}
            value={password}
            type="password"
          />
          <SubmitButtons
            buttonStyle={updateButton}
            buttonText="Update Password"
          />
        </form>

        {updated && (
          <div>
            <p>
              Your password has been successfully reset, please try logging in
              again.
            </p>
            <LinkButtons
              buttonStyle={loginButton}
              buttonText="Login"
              link="/login"
            />
          </div>
        )}
        <LinkButtons buttonText="Go Home" buttonStyle={homeButton} link="/" />
      </div>
    );
  }
}

ResetPassword.propTypes = {
  // eslint-disable-next-line react/require-default-props
  match: PropTypes.shape({
    params: PropTypes.shape({
      token: PropTypes.string.isRequired,
    }),
  }),
};
```

And here’s the three main pieces of this component that do the heavy lifting.

**The initial componentDidMount() lifecycle method**

```javascript
  async componentDidMount() {
    const {
      match: {
        params: { token },
      },
    } = this.props;
    try {
      const response = await axios.get('http://localhost:3003/reset', {
        params: {
          resetPasswordToken: token,
        },
      });
      if (response.data.message === 'password reset link a-ok') {
        this.setState({
          username: response.data.username,
          updated: false,
          isLoading: false,
          error: false,
        });
      }
    } catch (error) {
      console.log(error.response.data);
      this.setState({
        updated: false,
        isLoading: false,
        error: true,
      });
    }
  }
```

This method fires as soon as the page is reached. It extracts the token from the URL query parameters and passes it back to the server’s `reset` route to verify the token is legit.

Then, the server either responds with an "a-ok", this token is valid and associated with the user or a "no", the token’s no good anymore for some reason.

**The updatePassword() function**

```javascript
 updatePassword = async (e) => {
    e.preventDefault();
    const { username, password } = this.state;
    const {
      match: {
        params: { token },
      },
    } = this.props;
    try {
      const response = await axios.put(
        'http://localhost:3003/updatePasswordViaEmail',
        {
          username,
          password,
          resetPasswordToken: token,
        },
      );
      if (response.data.message === 'password updated') {
        this.setState({
          updated: true,
          error: false,
        });
      } else {
        this.setState({
          updated: false,
          error: true,
        });
      }
    } catch (error) {
      console.log(error.response.data);
    }
  };
```

This is the function that will fire if the user is authenticated and allowed to reset their password. It also accesses a specific route on the server called `updatePasswordViaEmail()` (I did this because I gave users a separate route to update their password while logged in to the app, as well), and once the updated password has been saved to the database, a success message response is sent back to the client.

**The render() method**

```javascript
  render() {
    const { password, error, isLoading, updated } = this.state;

    if (error) {
      return (
        <div>
          <HeaderBar title={title} />
          <div style={loading}>
            <h4>Problem resetting password. Please send another reset link.</h4>
            <LinkButtons
              buttonText="Go Home"
              buttonStyle={homeButton}
              link="/"
            />
            <LinkButtons
              buttonStyle={forgotButton}
              buttonText="Forgot Password?"
              link="/forgotPassword"
            />
          </div>
        </div>
      );
    }

    if (isLoading) {
      return (
        <div>
          <HeaderBar title={title} />
          <div style={loading}>Loading User Data...</div>
        </div>
      );
    }

    return (
      <div>
        <HeaderBar title={title} />
        <form className="password-form" onSubmit={this.updatePassword}>
          <TextField
            style={inputStyle}
            id="password"
            label="password"
            onChange={this.handleChange('password')}
            value={password}
            type="password"
          />
          <SubmitButtons
            buttonStyle={updateButton}
            buttonText="Update Password"
          />
        </form>

        {updated && (
          <div>
            <p>
              Your password has been successfully reset, please try logging in again.
            </p>
            <LinkButtons
              buttonStyle={loginButton}
              buttonText="Login"
              link="/login"
            />
          </div>
        )}
        <LinkButtons buttonText="Go Home" buttonStyle={homeButton} link="/" />
      </div>
    );
  }
```

The last piece of this component, is the `render()` method. Initially, while the token is being verified for its validity, the `loading` message shows.

If the link is invalid in some way, the `error` message shows on the screen with links back to the home screen or forgot password page.

If the user is authorized to reset their password, they get the new password input with the `updatePassword()` function attached to it, and once the server’s responded with success updating the password, the `updated` boolean is set to true and the `Your password has been successfully reset...` message is shown along with a login button.

### Backend code: reset password & update password

Ok, I’m in the home stretch now. Here’s the last two routes on the server side you’ll need. These correspond to the two methods I just walked through on the client side in the React `ResetPassword.js` component.

[**`resetPassword.js`**](https://github.com/paigen11/mysql-registration-passport/blob/master/api/routes/resetPassword.js)

```javascript
/* eslint-disable no-console */
/* eslint-disable max-len */
import Sequelize from 'sequelize';
import User from '../sequelize';

// eslint-disable-next-line prefer-destructuring
const Op = Sequelize.Op;

module.exports = (app) => {
  app.get('/reset', (req, res) => {
    User.findOne({
      where: {
        resetPasswordToken: req.query.resetPasswordToken,
        resetPasswordExpires: {
          [Op.gt]: Date.now(),
        },
      },
    }).then((user) => {
      if (user == null) {
        console.error('password reset link is invalid or has expired');
        res.status(403).send('password reset link is invalid or has expired');
      } else {
        res.status(200).send({
          username: user.username,
          message: 'password reset link a-ok',
        });
      }
    });
  });
};
```

This is the route that’s called on the `componentDidMount()` lifecycle method on the client side. It checks the `resetPasswordToken` passed from the link’s query parameters and date timestamp to ensure that everything’s good.

You’ll notice the `resetPasswordExpires` parameter has an odd looking `$gt: Date.now()` parameter. This is an [operator alias comparator](http://docs.sequelizejs.com/manual/tutorial/querying.html#operators-aliases), which [Sequelize](http://docs.sequelizejs.com/) allows me to use, and all the `$gt:` stands for is "greater than" whatever it is being compared to. In this instance, it’s comparing the current time to the expiration time stamp saved to the database when the reset password email was sent, to make sure the password’s being reset less than an hour after the email was sent.

As long as both parameters are valid for that user, the client is sent a successful response and the user can proceed with the password reset.

[**`updatePasswordViaEmail.js`**](https://github.com/paigen11/mysql-registration-passport/blob/master/api/routes/updatePasswordViaEmail.js)

```javascript
/* eslint-disable no-console */
import bcrypt from 'bcrypt';
import Sequelize from 'sequelize';
import User from '../sequelize';

// eslint-disable-next-line prefer-destructuring
const Op = Sequelize.Op;

const BCRYPT_SALT_ROUNDS = 12;

module.exports = app => {
  app.put('/updatePasswordViaEmail', (req, res) => {
    User.findOne({
      where: {
        username: req.body.username,
        resetPasswordToken: req.body.resetPasswordToken,
        resetPasswordExpires: {
          [Op.gt]: Date.now(),
        },
      },
    }).then(user => {
      if (user == null) {
        console.error('password reset link is invalid or has expired');
        res.status(403).send('password reset link is invalid or has expired');
      } else if (user != null) {
        console.log('user exists in db');
        bcrypt
          .hash(req.body.password, BCRYPT_SALT_ROUNDS)
          .then(hashedPassword => {
            user.update({
              password: hashedPassword,
              resetPasswordToken: null,
              resetPasswordExpires: null,
            });
          })
          .then(() => {
            console.log('password updated');
            res.status(200).send({ message: 'password updated' });
          });
      } else {
        console.error('no user exists in db to update');
        res.status(401).json('no user exists in db to update');
      }
    });
  });
};
```

This is the second route that’s called when the user submits his/her password to be updated.

Once again, I find the user in the database and double check that the `resetPasswordToken` also exists and is valid (the `username` was passed back to the client from the reset route above and held in the app’s state until the update function was called), I hash the new password using my `bcrypt` module (just like my Passport.js middleware does when a new user is being written into the database initially),update that user’s `password` in the database with the new hash and set both the `resetPasswordToken` and `resetPasswordExpires` columns back to null, so the same link can’t be used more than once.

As soon as that’s complete, the server sends back a 200 response with the success message, `Password updated` for the client.

And you’ve successfully reset a user’s password via email. Not too tough.

---

## Conclusion

At first glance, resetting a user’s password via an email link seems a bit daunting. But Nodemailer helps make a major factor (the emailing bit) simple. And once that’s done it’s just a few routes on the server side and inputs on the client side to get that password updated for the user.

Check back in a few weeks, I’ll be writing about using Puppeteer and headless Chrome for end-to-end testing or something else related to web development.

If you’d like to make sure you never miss an article I write, sign up for my newsletter here: https://paigeniedringhaus.substack.com

Thanks for reading, I hope this gives you an idea of how to use Nodemailer to send password reset emails for a MERN application.

---

## References & Further Resources

- [Nodemailer](https://nodemailer.com/about/)
- Nodemailer, [GitHub](https://github.com/nodemailer/nodemailer)
- Nodemailer, [npm](https://www.npmjs.com/package/nodemailer)
- [MERN app with Nodemailer repo](https://github.com/paigen11/mysql-registration-passport)