Code snippets, tutorials and software engineering.

Being able to send an email from a business application or personal website is a common requirement for most projects. This provides visitors of your website a way to contact you or your company directly without having to leave the website and do it manually via Gmail, Outlook, Yahoo, etc.

This article will go over how to implement this functionality into a simple contact form. I am assuming you have some basic knowledge of JavaScript, TypeScript, Nextjs, and Nodejs  before starting this tutorial. This may be implemented in a create-react-app or any other front end framework as well.

We will use NodeMailer to easily achieve implementing this email feature into our code. Nodemailer is a Nodejs module that simplifies sending emails from your project server. Nowadays, it is the solution most Node.js users turn to by default. Its main features include (but are not limited to):

platform-independence

HTML content and embedded image attachments

different transport methods besides SMTP support.

STEP 1) Boilerplate Code

We are going to start with creating the basis of our project. Your project can be started with create-next-app - a CLI that tool enables you to quickly start building a new Next.js application, with everything set up for you.

Go to your terminal of choice and enter the following command:

npx create-next-app --ts app-name

This will create a folder with your chosen “app-name” on your computer that contains all of our boilerplate code. Notice that Next has detected Typscript will be used in your project, and has appropriately used the ts or tsx extensions for all its files. You will end up with the basic starter code that includes a pages directory, public directory, styles directory and more.

Cd into your project in your terminal and download the necessary dependencies with this command:

npm i axios nodemailer @types/nodemailer

We will go over the dependencies throughout the article.

Step 2) Creating the Contact Form

Open the project into your code editor of choice - here, we will use VS Code. Open up the /pages directory. Here we will create a file titled contact.tsx that will hold all our code relating to the visual presentation of the contact form.

Now, we will add the code for our contact form that will contain several text inputs - one for the person’s name, one their email address, one for the subject of the email and one for the message of the email. The form will also contain a button to submit the data your user has typed out.

Note: We have included some additional imports which will be used later on.

import { ChangeEvent, SyntheticEvent, useState } from 'react';
import styles from '../styles/Contact.module.css';
import axios from 'axios';

export default function Contact() {
  
  return (
    <main className={`main`}>
      <div className={'container'}>
        <div className={'content'}>
          <h1>Contact</h1>
          <div>
            <form className={styles.contactform}>
              <div className={styles.form_group}>
                <label className={styles.label} htmlFor='name'>
                  Name
                </label>
                <br />
                <input
                  className={styles.inputfield}
                  type='text'
                  name='name'
                  id='name'
                />
              </div>
              <div className={styles.form_group}>
                <label className={styles.label} htmlFor='email'>
                  Email
                </label>
                <br />
                <input
                  className={styles.inputfield}
                  type='email'
                  name='email'
                  id='email'
                />
              </div>
              <div className={styles.form_group}>
                <label className={styles.label} htmlFor='subjet'>
                  Subject
                </label>
                <br />
                <input
                  className={styles.inputfield}
                  type='text'
                  name='subject'
                  id='subject'
                />
              </div>
              <div className={styles.form_group}>
                <label className={styles.label} htmlFor='message'>
                  Message
                </label>
                <br />
                <textarea
                  className={styles.inputfield}
                  name='message'
                  id='message'
                  cols={30}
                  rows={10}></textarea>
              </div>
              <div
                className=
                {`${styles.form_group}
                ${styles.submit_button_div}`}>
                <button className=
                {styles.submit_button}>Send</button>
              </div>
            </form>
          </div>
        </div>
      </div>
    </main>
  );
}


Notice I have also imported a CSS Module and titled the returning object styles. When creating your project with create-next-app, Nextjs automatically implements a couple of demo CSS modules to style the initial index page. I’ve continued implementing CSS modules in our contact form as it is a clean way to achieve locally scoped class names for styling.

The file containing all of the contact form’s CSS should be under the /styles directory. Under /styles, create the Contact.module.css file and add the following code:

.container {
  width: 100%;
  max-width: 1060px;
  margin: 0 auto;
}

.main {
  width: 100%;
  z-index: 0;
}

.content {
  max-width: 820px;
  margin: 0 auto;
  padding-top: 80px;
  padding-bottom: 100px;
}

.contactform {
  width: 600px;
  height: auto;
  background-color: #ffe6e8;
  padding: 20px;
  position: relative;
  margin: 120px auto 20px auto;
}

.contactform::after {
  content: '';
  width: 73%;
  height: 100%;
  display: block;
  position: absolute;
  bottom: -20px;
  left: -20px;
  background-color: #fc7a77;
  z-index: -1;
}

.form_group {
  padding: 20px;
}

.label {
  color: #f94951;
}

.inputfield {
  padding: 8px 20px;
  width: 100%;
  border: #f94951 1px solid;
  color: #f94951;
  background-color: #ffe6e8;
}

.inputfield:focus {
  background-color: #fafafa;
}

.submit_button {
  background-color: #f94951;
  color: #fafafa;
  border: none;
  padding: 7px 30px;
}

.submit_button_div {
  display: flex;
  justify-content: flex-end;
}


The css is used to add some color and modernist styling to the presentation. The resulting contact form will look like this.

Step 3) Setting Up Our State


Now we will work on implementing some state into our frontend code. The state we will build for our form is meant to hold the input that the user types into the various input fields. We are using what are called Controlled inputs, which are inputs that get their value from a single source of truth - here being our state. We will start off by importing useState from react at the top of our file.

import { useState } from 'react'

Inside of our export function, we will initiate our state with an empty object.

const [form, setForm] = useState({});

As the input fields in our form are updated with the user’s information, our state will need to add to or update the initial object accordingly. To do so, we will create a function called handleChange to handle passing the values from our input fields to the state.

 const handleChange = (
    e: ChangeEvent<HTMLInputElement> | ChangeEvent<HTMLTextAreaElement>
  ) => {
    const { value, name } = e.target;
    setForm({ ...form, [name]: value });
  };

Let’s go over what is happening in the code we just wrote. The e variable holds information about the event that was triggered, here being the content of our form inputs being changed. Type safety is added by implicitly typing our e variable, in our case it can be a ChangeEvent triggered by an HTMLInput Element or a ChangeEvent triggered by an HTMLTextArea Element.

The value and name from the changed input is destructured from e.target, and is used to update our state with the setForm() function provided by the useState() hook. We are copying the original contents of our form with a spread operator ... and introducing the updated/added input field’s name and value to our state.

Next, we will need to add this function inside of the onChange attribute on all of our input fields. onChange is an attribute set in our input fields that runs a function whenever their value changes in any way. There is no need to pass a parameter, as the parameter will be the Event object mentioned earlier, our e variable. All of our input fields should now look something like this

 		<input
                  onChange={handleChange}
                  className={styles.inputfield}
                  type='text'
                  name='name'
                  id='name' />

Step 5) Setting up the backend with NodeMailer

Now that we have a state in the frontend to handle the values in our form, we will begin to set up the backend that will handle sending our emails. Within the /pages, open the /api directory and create the file contact.ts. The contact.ts file within this directory will create a new api endpoint that's accessed with the following path:  /api/contact.

This file will contain the code needed to create an endpoint with the purpose of emailing the data our users typed into the form to. We will import the nodemailer module and the appropriate types for our code into contact.ts.

import type { NextApiRequest, NextApiResponse } from 'next';
import nodemailer, { Transporter } from 'nodemailer';
import SMTPTransport from 'nodemailer/lib/smtp-transport';

Inside of our file, we will export an anonymous async function and provide the request and response with their respective types as parameters.

export default function async(
  req: NextApiRequest,
  res: NextApiResponse
) { }

Inside of our function, we can now begin using NodeMailer. Create a variable called transporter and set it equal to nodemailer.createTransport() with an empty object as the parameter.

let transporter: Transporter = nodemailer.createTransport({});

Our transporter object helps us configure how we want to send our emails. It is currently set to use an empty object, but we will now use the following properties - host, port and auth.

Host - The hostname or IP address of the SMTP server to connect to.

Port - The port to connect to. 465 is for SMTP communication

Auth - Defines the authentication data for the host.

let transporter: Transporter = nodemailer.createTransport({
    host: 'smtp.gmail.com',
    port: 465,
    auth: {
      user: 'sample-email@gmail.com',
      pass: 'abc123',
    },
  });

In this example we will be using the Gmail SMTP to send our emails. You will need to provide the username and password for the gmail account you want the transporter to send emails from. One recommendation is to create an email account with the sole purpose of sending emails from your contact form and use the login credentials in the auth property.

Setting Up Your Gmail Account

Since we are using Gmail in this example, we need to go through an additional step outside of our code. You will need to configure your gmail account’s security settings to grant NodeMailer access to use your account. Otherwise Gmail will assume it is a security breach.

Access the Google Account dashboard associated with your email. On the left hand side you should see the Security tab in the menu. Click on it and look for the ‘Less secure app access’ section. It should currently be set to Off, toggle it to On.

Now we’re ready for the next step!

Once you have your auth information set up, create a variable called options and set it equal to an empty object.

let options= {};

This options object will hold the typical email information, which will come from the data sent from the contact form. The properties we will set up are from, to, subject and text.

From: The email account that will be sending the email.

To: The email recipient.

Subject: The subject sent along with the email.

Text: The plain-text body of the email.

We will use template strings to pass along these pieces of data from our request's body property.

 let options = {
    from: `${req.body.email}`,
    to: 'you@gmail.com',
    subject: `${req.body.subject}`,
    text: `${req.body.message}`,
  };

Now that we have configured the transporter and set up the content of the email, it's time to actually trigger the sending of it. After the options object, create a new variable called result and set the type as SMTPTransport.SentMessageInfo. Since we created an async function, we will use the await keyword and call the sendMail property available on our transporter. We will set our options as the parameter, like so:

 let result: SMTPTransport.SentMessageInfo = 
 await transporter.sendMail(options);

The sendMail() method from our transporter is what emails the data provided from our form. After completing our code, we will wrap all of our code in a trycatch for error handling. If the code in the try block is successful, we will end our function with a status of 200 to signal a successful api call. Otherwise, an error will be thrown and will respond will a status of 400 from our catch block. Your final code should look something like this.

import type { NextApiRequest, NextApiResponse } from 'next';
import nodemailer, { Transporter } from 'nodemailer';
import SMTPTransport from 'nodemailer/lib/smtp-transport';

export default function async(req: NextApiRequest,  res:NextApiResponse) { 
	try {
        let transporter: Transporter = nodemailer.createTransport({
        	host: 'smtp.gmail.com',
        	port: 465,
            auth: {
              user: 'sample-email@gmail.com',
              pass: 'abc123',
            },
      	});
        let options = {
            from: `${req.body.email}`,
            to: 'you@gmail.com',
            subject: `${req.body.subject}`,
            text: `${req.body.message}`,
      	};
       let result: SMTPTransport.SentMessageInfo = await 						transporter.sendMail(options);
       res.status(200);
    }
    catch(error){
    	res.status(400);
    }
}

Step 6) Call the api endpoint in the front end

Now that we have created the api endpoint in the backend, we can set up the contact form to send a request when the user submits the form. Underneath our handleChange function, create an async function called handleSubmit with an event parameter and type it accordingly. handleSubmit() will make a post request to our new api endpoint /api/contact with axios once the user clicks on the submit button in our form.

If you are not familiar, Axios is a Javascript library used to make HTTP requests from node.js or from the browser that also supports the ES6 Promise API. Using axios to make this request requires you to access the post() method on our axios object. Then, provide the following parameters: the URL ‘/api/contact’ and the data in the form of an object, coming from the state we set up earlier.

const handleSubmit = async (e: SyntheticEvent) => {
    e.preventDefault();
    try {
      let res = await axios.post('/api/contact', form);
      alert.log(‘Success!’);
    } catch (error) {
      alert.log(‘An error occurred. Please try again.’);
    }
  };

Here we will wrap our code in a trycatch again for error handling. This is helpful in case an error occurs when attempting to submit our form data to the backend. If an error occurs, the user will see a popup with your error message. Otherwise, they should see a popup with a success message. In addition to the trycatch, add e.preventDefault() to prevent the default action that occurs when you submit the form - a browser reload/refresh.

Once you test out your form, you should see the success message like this.

That's it for the tutorial! If everything is set up correctly, especially the NodeMailer code, then users will be able to email you any inquiries via your new contact form. Continue testing it out and make sure you receive it in your gmail account.

You’ve successfully subscribed to Into Code
Welcome back! You’ve successfully signed in.
Great! You’ve successfully signed up.
Your link has expired
Success! Check your email for magic link to sign-in.