Node.js Contact Form Using Express v.4.x, Nodemailer and Gmail

This article is a continuation of the HTML/CSS Contact Form. If not familiar with JavaScript then read this tutorial which covers a version of JavaScript compatible with most Internet browsers. If not familiar with Node.js then read this tutorial which covers installing Node.js, Express.js and more.

Node.js Installation

This article will use an installation of Ubuntu Linux v.18.04 to proceed with. It can safely be installed on a virtual machine if hardware is not available.

First, create a directory to work from. From a command prompt/terminal: mkdir nodemailer_project and cd nodemailer_project.

If not already at the terminal, open up a terminal window. Execute node --version. This will show if Node.js is already installed on the machine.

If Node.js is not already installed, then issue a sudo apt install nodejs command which will install it. This can also be used to update it.

One of the technologies that the text editor Visual Studio Code was designed for is Node.js. Use the Ubuntu Software library to install Visual Studio Code.

Note that if working on a Linux server then all these commands can be executed from a terminal just make sure to be in the project folder when executing them.

Installing Express v.4.x and Nodemailer for Node.js

Express and Nodemailer are a requirement for this example.

Open Visual Studio Code and open the nodemailer_project folder. To install NPM Express with Nodemailer, issue a sudo apt-get install npm, npm install express and npm install nodemailer from the Terminal window in Visual Studio Code. If not using Visual Studio Code just make sure that these installation commands are issued in the project folder. The command npm init is optional.

Code Examples

First, an example without the Nodemailer code. Create a file named index.js. Then create a folder named public and create a file in it named index.html. Paste (or download) the following code into the index.js file.

const express = require("express");
const path = require("path");

const app = express();


// body parser middleware
app.use(express.json());
app.use(express.urlencoded( { extended: false } )); // this is to handle URL encoded data
// end parser middleware


// custom middleware to log data access
const log = function (request, response, next) {
	console.log(`${new Date()}: ${request.protocol}://${request.get('host')}${request.originalUrl}`);
	console.log(request.body); // make sure JSON middleware is loaded before this line
	next();
}
app.use(log);
// end custom middleware


// enable static files pointing to the folder "public"
// this can be used to serve the index.html file
app.use(express.static(path.join(__dirname, "public")));


// HTTP POST
app.post("/ajax/email", function(request, response) { // this will be used to send the emails
	response.json( { message: "You attempted to send a message but this is not implemented yet." } );
});


// set port from environment variable, or 8000
const PORT = process.env.PORT || 8000;

app.listen(PORT, () => console.log(`listening on port ${PORT}`));

Next, paste (or download) the following code into the file named index.html. Have the index.js file open and selected. Run the index.js code file by hitting Ctrl+F5 in Visual Studio Code or by entering the command node index.js at the command prompt/terminal. Next, open a browser and navigate to http://ipaddress:8000/. "ipaddress" can be the ip address of the machine or it can be "localhost" if working on the machine itself.

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<meta http-equiv="X-UA-Compatible" content="ie=edge">
	<title>Home</title>
	<style>
		*, *::before, *::after {
			box-sizing: border-box;
		}
		body {
			font-family: sans-serif;
			font-size: 16px;
			background-color: #fff;
		}
		h1 {
			font-variant: small-caps;
		}
		.contact {
			visibility: hidden;
			opacity: 0;
			position: absolute;
			left: 0;
			top: 0;
			right: 0;
			bottom: 0;
			transition: .5s;
			background-color: #fff;
		}
		.contact section {
			text-align: right;
			padding: 10px;
		}
			.contact section a {
				border: none;
				text-decoration: none;
				outline: none;
				color: #e05e02;
			}
		.contact:target {
			visibility: visible;
			opacity: 1;
		}
		.contact-form {
			position: absolute;
			max-width: 500px;
			left: 50%;
			top: 50%;
			transform: translate(-50%,-50%);
			display: flex;
			flex-direction: column;
			box-shadow: 0 0 20px rgba(0,0,0,0.2);
			width:90%;
			border-radius: 20px;
			overflow: hidden;
		}
		.contact-form-input,
		.contact-form button {
			font-family: inherit;
			text-transform: uppercase;
			font-weight: bold;
			font-size: 12px;
			padding: 24px;
			letter-spacing: 1px;
			border: none;
			width: 100%;
			outline: none;
		}
		.contact-form button {
			background-color: #e05e02;
			padding: 20px;
			color: #fff;
		}
		.contact-form div:nth-child(1),
		.contact-form div:nth-child(2) {
			position: relative;
		}
		.contact-form div:nth-child(1)::after,
		.contact-form div:nth-child(2)::after {
			content: "";
			position: absolute;
			left: 0;
			right: 0;
			bottom: 0;
			height: 1px;
			background-color: rgba(0,0,0,0.15);
		}
		.contact-form textarea {
			overflow: auto;
			height: 100px;
			text-transform: none;
		}
	</style>
	<script>
		function submitEmailForm(form) {
			var obj = new XMLHttpRequest();
			obj.onreadystatechange = function(){
				if(obj.readyState == 4){
					if(obj.status == 200){
						var res = JSON.parse(obj.responseText);
						alert(res.message);
					}
					else{
						alert("XMLHttp status " + obj.status + ": " + obj.statusText);
					}
				}
			};
			obj.open("post", form.action, true);
			obj.setRequestHeader("Content-Type", "application/json"); // NOTICE: "application/json"
			obj.send(JSON.stringify({ name: form.name.value, email: form.email.value, message: form.message.value }));
			return false;
		}
	</script>
</head>
<body>
	<h1>Home Page</h1>
	<p><a href="#contact-id">click here for the contact form</a></p>
	<div class="contact" id="contact-id">
		<section><a href="#!">[ close ]</a></section>
		<form action="/ajax/email" class="contact-form" method="POST" onsubmit="return submitEmailForm(this);">
			<div>
				<input type="text" name="name" placeholder="name" class="contact-form-input" required />
			</div>
			<div>
				<input type="email" name="email" placeholder="email" class="contact-form-input" required />
			</div>
			<div>
				<textarea name="message" class="contact-form-input" placeholder="MESSAGE" required></textarea>
			</div>
			<div>
				<button type="submit">send</button>
			</div>
		</form>
	</div>
</body>
</html>

Adding the Nodemailer Code

Using Nodemailer with Gmail really is very simple. The connection to Gmail is secure and reliable. Here, the contact form mails the message back to the same Gmail account but it could be sent to another account.

Because the code here is enabled for secure connections, there is no need to enable "allow less secure apps" on the gmail account.

const express = require("express");
const path = require("path");
const nodemailer = require("nodemailer");

const app = express();


// body parser middleware
app.use(express.json());
app.use(express.urlencoded( { extended: false } )); // this is to handle URL encoded data
// end parser middleware


// custom middleware to log data access
const log = function (request, response, next) {
	console.log(`${new Date()}: ${request.protocol}://${request.get('host')}${request.originalUrl}`);
	console.log(request.body); // make sure JSON middleware is loaded first
	next();
}
app.use(log);
// end custom middleware


// enable static files pointing to the folder "public"
// this can be used to serve the index.html file
app.use(express.static(path.join(__dirname, "public")));


// HTTP POST
app.post("/ajax/email", function(request, response) {
  // create reusable transporter object using the default SMTP transport
	const transporter = nodemailer.createTransport({
		host: "smtp.gmail.com",
		port: 465,
		secure: true,
		auth: {
			user: "your_account@gmail.com", // this should be YOUR GMAIL account
			pass: "your_password" // this should be your password
		}
	});

	var textBody = `FROM: ${request.body.name} EMAIL: ${request.body.email} MESSAGE: ${request.body.message}`;
	var htmlBody = `<h2>Mail From Contact Form</h2><p>from: ${request.body.name} <a href="mailto:${request.body.email}">${request.body.email}</a></p><p>${request.body.message}</p>`;
	var mail = {
		from: "your_account@gmail.com", // sender address
		to: "your_account@gmail.com", // list of receivers (THIS COULD BE A DIFFERENT ADDRESS or ADDRESSES SEPARATED BY COMMAS)
		subject: "Mail From Contact Form", // Subject line
		text: textBody,
		html: htmlBody
	};

	// send mail with defined transport object
	transporter.sendMail(mail, function (err, info) {
		if(err) {
			console.log(err);
			response.json({ message: "message not sent: an error occured; check the server's console log" });
		}
		else {
			response.json({ message: `message sent: ${info.messageId}` });
		}
	});
});


// set port from environment variable, or 8000
const PORT = process.env.PORT || 8000;

app.listen(PORT, () => console.log(`listening on port ${PORT}`));

Configure the Node.js App to Run Automatically by Installing PM2 (Process Manager)

Issue a sudo npm install pm2 -g command to install PM2. After it has installed, issue a pm2 start index.js command to start the process in the background. Run the command pm2 startup and follow the directions on the screen then run the command pm2 save.

Coding Videos

https://youtu.be/03vkkApu2is

https://youtu.be/UkL75knjcCY

This is the end of this article.