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
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 serverdotenv
: Reads configuration data and serve as a great way to keep sensitive data away from accessible codemailgun-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.