Sending Emails in Node.js with Mailgun and React

Email is an essential method of business communication that is very fast, cheap, and top-free organic. When building products, emails can greatly provide your business with an effective and efficient medium to transmit various kinds of data, electronically.

In this article, we will learn how to build a form for sending emails using the Mailgun service. For the frontend application, we will use React, on the backend, we will make use of Node.js, to make asynchronous requests, we will make use of Axios, and to show notifications, we will make use of the React-Toastify package. At the end of the day, we should have built a mail service that works just like this:

Note: You can get access to the repository for this project you will build bit by bit today, and play around with the implementation using this link on GitHub.

Let's get started!

Project Setup

The first thing we will build out today is the frontend application, using React.

Getting started with the React library is simple as including a JavaScript file (a CDN) into an HTML file. But for a real-world and large-scale application, the React CLI is the better way to get started. We will use the React CLI in our tutorial today.

In this article we'll use a CLI tool built by the React team to help facilitate the rapid development of React applications. To install the React CLI, run the following command - npm install -g create-react-app.

Now, let us create the project and name it react-node-email-app, using the command below:

$ npx create-react-app react-node-email-app

This will create a starter template to build our project. You can view this template by getting into your project directory and then running the development server:

$ cd react-node-email-app
$ yarn start

We can then view our frontend in the browser at localhost:3000.

Building the Project UI

Now, we can begin to build the frontend of the application. We'll start by building the form through which emails can be sent.

We will do this in the App.js file, so that it is updated to become:

import './App.css';
import { useState } from 'react';

function App() {
  const [email, setEmail] = useState('')
  const [subject, setSubject] = useState('')
  const [message, setMessage] = useState('')
  return (
    <div className="App">
      <section>
        <form>
          <h1>Send Email</h1>
          <div className='form-wrapper'>
          <div>
            <label htmlFor='email'>Email Address</label>
            <input onChange={(e)=>setEmail(e.target.value)} type="email" id="email"></input>
          </div>
          <div>
            <label htmlFor='subject'>Email Subject</label>
            <input onChange={(e)=>setSubject(e.target.value)} type="text" id="subject"></input>
          </div>
          <div>
            <label htmlFor='message'>Message Body</label>
            <textarea onChange={(e)=>setMessage(e.target.value)} type="text" id="message"></textarea>
          </div>
          <div>
            <button type='submit'>Send Email</button>
          </div>
          </div>
        </form>
      </section>
    </div>
  );
}

export default App;

Here we have created a form with three input fields, one for inputting the receiver's email, another for the subject of the email, and the last one for entering the main message of the email.

In the various input fields, we have set up a state to handle the change in the input box - to catch any change made when a user enters a character into the input field using the onChange event listener.

We have also created a button for the purpose of making an email submission.

To beautify the form, we've update the App.css file with the following CSS styles, so that its contents look like this:

.App {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #282c34;
}
.App section form {
  min-width: 25rem;
  margin: 0 auto;
  border: solid 1px #bdbdbd;
  border-radius: 8px;
  padding: 2rem;
}
form h1 {
  text-align: center;
  color: #ffffff;
}
form .form-wrapper {
  margin: 0 auto;
}

form .form-wrapper > div {
  margin-bottom: 1rem;
}
form .form-wrapper > div > label {
  margin-bottom: 0.5rem;
  color: #ffffff;
  display: block;
}
form .form-wrapper > div > input, form .form-wrapper > div > textarea {
  padding: 0.5rem;
  border-radius: 4px;
  border: none;
  outline: none;
  min-width: 20rem;
  font-family: Arial, Helvetica, sans-serif;
}
form .form-wrapper > div > button {
  padding: 1rem 2.5rem;
  color: white;
  background: rgb(4, 144, 199);
  border-radius: 4px;
  border: none;
  cursor: pointer;
}

To improve the performance of the application, an onSubmit handler event function has to be defined on the form element to prevent the default behavior of forms that actually refreshes a page when its button is clicked. It's not ideal that our page is refreshed when the button is clicked since we're sending the request in the background, which is a better experience for the user.

It is also ideal that a user should not be able to send an email if he/she has not inputted any email, subject, or message. For that, we have to some input validation to check if those fields are empty. If they are, we return an error with the message "Please fill email, subject, and message".

To do this, we make use of the toastify package in our application. We need to install it by running the following command in the project terminal:

$ yarn add react-toastify

Next, update App.js with the submitHandler function:

import "./App.css";
import { useState } from "react";
import { toast, ToastContainer } from "react-toastify";
import 'react-toastify/dist/ReactToastify.css'

function App() {
  const [email, setEmail] = useState("");
  const [subject, setSubject] = useState("");
  const [message, setMessage] = useState("");

  const submitHandler = async (e) => {
    e.preventDefault();
    if (!email || !subject || !message)
      return toast.error(
        "Please make sure to fill the email address, email subject, and message"
      );
  };

  return (
    <div className="App">
      <section>
        <ToastContainer position="top-center" limit={1} />
        <form onSubmit={submitHandler}>
          <h1>Send Email</h1>
          <div className="form-wrapper">
            <div>
              <label htmlFor="email">Email Address</label>
              <input
                onChange={(e) => setEmail(e.target.value)}
                type="email"
                id="email"
              ></input>
            </div>
            <div>
              <label htmlFor="subject">Email Subject</label>
              <input
                onChange={(e) => setSubject(e.target.value)}
                type="text"
                id="subject"
              ></input>
            </div>
            <div>
              <label htmlFor="message">Message Body</label>
              <textarea
                onChange={(e) => setMessage(e.target.value)}
                type="text"
                id="message"
              ></textarea>
            </div>
            <div>
              <button type="submit">Send Email</button>
            </div>
          </div>
        </form>
      </section>
    </div>
  );
}

export default App;

Now, each time a user clicks the button to send an email, without prefilling the email address, email subject, and message input fields, the toast message prompts him/her to do all that is required.

Since we also need the axios library for making AJAX requests, we will also need to install it:

$ yarn add axios

Next, create a try/catch block to handle the asynchronous request for sending an email.

App.js is now updated to become:

import "./App.css";
import { useState } from "react";
import { toast, ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import axios from "axios";

function App() {
  const [email, setEmail] = useState("");
  const [subject, setSubject] = useState("");
  const [message, setMessage] = useState("");
  const [loading, setLoading] = useState("");

  const submitHandler = async (e) => {
    e.preventDefault();
    if (!email || !subject || !message)
      return toast.error(
        "Please make sure to fill the email address, email subject, and message"
      );
    try {
      setLoading(true);
      const { data } = await axios.post(`/api/email`, {
        email,
        subject,
        message,
      });
      setLoading(false);
      toast.success(data.message);
    } catch (error) {
      setLoading(false);
      toast.error(
        error.response && error.response.data.message
          ? error.response.data.message
          : error.message
      );
    }
  };

  return (
    <div className="App">
      <section>
        <ToastContainer position="top-center" limit={1} />
        <form onSubmit={submitHandler}>
          <h1>Send Email</h1>
          <div className="form-wrapper">
            <div>
              <label htmlFor="email">Email Address</label>
              <input
                onChange={(e) => setEmail(e.target.value)}
                type="email"
                id="email"
              ></input>
            </div>
            <div>
              <label htmlFor="subject">Email Subject</label>
              <input
                onChange={(e) => setSubject(e.target.value)}
                type="text"
                id="subject"
              ></input>
            </div>
            <div>
              <label htmlFor="message">Message Body</label>
              <textarea
                onChange={(e) => setMessage(e.target.value)}
                type="text"
                id="message"
              ></textarea>
            </div>
            <div>
              <button disabled={loading} type="submit">
                {loading ? "Sending..." : "Send Email"}
              </button>
            </div>
          </div>
        </form>
      </section>
    </div>
  );
}

export default App;

Building the Backend Application using Node

At this point, we have successfully implemented the frontend of the application. Moving on, we will begin to work on the backend using Node.

First, create a new directory in the root of the project called backend.

Now, cd into the backend directory and run npm init from the terminal in order to begin creating a Node app.

$ npm init --y
Free eBook: Git Essentials

Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

Note: The --y option tells NPM to answer "yes" to all of the questions posed by the init command. This essentially gives you the default settings for your package.json file.

This creates a package.json file that enables the management of the application dependencies of the backend project.

Next, install the following packages:

  • express: Creates a web server
  • dotenv: Reads configuration data and serve as a great way to keep sensitive data away from accessible code
  • mailgun-js: Enables the sending of emails using Mailgun
$ yarn add express dotenv mailgun-js

With these packages now installed, create two new files server.js and .env in the backend folder.

In the .env file, we will keep the domain and the API key for Mailgun.

Building the server

In this server.js file, the objective is to create a server using Express.js. Therein, we import the express, dotenv, and mailgun-js packages.

server.js is updated to become:

const express = require("express");
const dotenv = require("dotenv");
const mg = require("mailgun-js");

dotenv.config();

const mailgun = () =>
  mg({
    apiKey: process.env.MAILGUN_API_KEY,
    domain: process.env.MAILGUN_DOMAIN,
  });
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.post("/api/email", (req, res) => {
  const { email, subject, message } = req.body;
  mailgun()
    .messages()
    .send(
      {
        from: "John Doe <[email protected]>",
        to: `${email}`,
        subject: `${subject}`,
        html: `<p>${message}</p>`,
      },
      (error, body) => {
        if (error) {
          console.log(error);
          res.status(500).send({ message: "Error in sending email" });
        } else {
          console.log(body);
          res.send({ message: "Email sent successfully" });
        }
      }
    );
});

const port = process.env.PORT || 4000;
app.listen(port, () => {
  console.log(`App is served at port ${port}`);
});

Here, we have called the mg function and assigned it to mailgun. In the mg function, we pass the API key and domain from the environment variable and use mailgun to send emails.

After that, the Express app is created, and two middleware express.json() and express.urlencoded are used to get the payload using Express from the API request and convert it into req.body.

Next, a POST route with the path /api/email is created, and therein, a function to accept a request and response is defined. The email, subject, and message can then be extracted from the req.body object. These are the data that the user enters from the input fields on the frontend application.

Once this is extracted, the messages method from the mailgun function initially set up is called, after which the send method is called. In the send method, an object that holds the following properties is passed:

  • from: The name that users see in the "from" section of an email.
  • to The email address to which the email is being sent (the email entered in the input field).
  • subject: The subject entered.
  • html: The HTML markup that defines the message to be sent to the email address.

The second parameter passed in the send method is a function that accepts the error and body. In this function we console.log the error, set the status code to 500, and send a message that says "Error in sending email". Otherwise we log the body and send a message that says "Email sent successfully" on success.

At the very end of the file, we get the port from the .env file and call app.listen() to start the server.

Defining the API keys

The first step to defining the API keys for using Mailgun in your application is to create a free account, which takes 57 seconds according to their official website.

After you have created an account, an API key for the account you created will be sent to you, while you can get access to the domain by navigating to the sending tab on the side nav of the dashboard.

Copy both the sandbox link and the API key and set the value in your .env file, as I have done for mine:

// ./backend/.env

MAILGUN_API_KEY=12345678901234567890123456789012-12345678-12345678
MAILGUN_DOMAIN=sandboxabcdefghijklmnopqrstuvwxyz.mailgun.org

Next, go back to the overview page of the domain by clicking the link. The page should look like this:

Therein, at the right side of the page, enter the email address you wish to test sending an email to - to create an authorized recipient.

Clicking the "Save Recipient" button should trigger an email that looks like shown below, to be sent:

Click the "I Agree" button, and you should be redirected to an external page:

Click the "Yes" button to proceed to activate the recipient address. Now that address is able to start receiving emails through Mailgun.

Finally, we have successfully set up Mailgun and built the backend using Node.

Now, you can delightfully spin up the Express server by running the following code from the backend directory:

$ node server.js

Connecting the Node App to the React App

To connect the frontend to the backend, head to the package.json file that lies in the root directory of the project for the React app. Then, under the name property, add a new property called proxy, which should have the value of the port for the backend server. It should look something like this:

"proxy": "http://localhost:4000/"

With that being added, all asynchronous requests we make from the frontend get redirected to the backend server that we have created.

Restart the frontend development server to reload the app.

Go ahead to test out all our implementation thus far, by inputting an email address (the recipient you activated earlier), email subject, and message, then hit the "Submit" button. This should show a success toast to say the email was successful.

And in the inbox of the recipient address, you should receive an email too, just like I did:

Wrapping Up

Sending emails remains very useful for cases such as resetting a password, welcoming a user to your application, confirming an order, etc., and in this article, we have learned how easy it is to send emails in a Node.js application using Mailgun. We see how easy it is to work with Mailgun and how user-friendly and fast it is to create an account to get started.

Next time you find yourself in need to build an email infrastructure for your project, feel free to check out mail sending tools like Mailgun. Need help using the tool, feel free to reach out to me, or anyone in the developer community, I’m sure they would be glad to help.

Additional Resources

Last Updated: February 8th, 2023
Was this article helpful?

Improve your dev skills!

Get tutorials, guides, and dev jobs in your inbox.

No spam ever. Unsubscribe at any time. Read our Privacy Policy.

Uchechukwu AzubukoAuthor

Uchechukwu Azubuko is a Software Engineer and STEM Educator passionate about making things that propel sustainable impact and an advocate for women in tech.

He enjoys having specific pursuits and teaching people better ways to live and work - to give them the confidence to succeed and the curiosity required to make the most of life.

© 2013-2024 Stack Abuse. All rights reserved.

AboutDisclosurePrivacyTerms