Michael Curran
['Hip', 'Hip'] - A Web Dev Blog

Follow

['Hip', 'Hip'] - A Web Dev Blog

Follow
Send Emails From Web Forms in NextJS

Send Emails From Web Forms in NextJS

Using Twilio SendGrid with NextJS to capture and send form data straight to your email

Michael Curran's photo
Michael Curran
ยทMay 31, 2022ยท

6 min read

Table of contents

  • Intro
  • Preview
  • Setting things up
  • The Code
  • Next Steps & Final Thoughts

Intro

Web Forms are a major part of any website and there are lots of ways to handle the data they hold. In this post we are going to explore sending Emails from our web forms within a NextJS application using Twilio SendGrid. You'll need an account with SendGrid Here to get an API key before getting started.

Preview

Let's take a quick look at what we are going to be building for this post. We will be making a simple contact form that will take in a few user inputs and send any valid responses to a provided email. To keep things simple we will add some really minimal styling and try to organize our code in just a few files.

output.gif

Setting things up

  • Create NextJS Application
    yarn create next-app sendgrid-example
  • Add needed packages
    yarn add @sendgrid/mail react-hook-form
  • Create necessary pages
    touch pages/api/sendEmail.js && touch .env.local && touch pages/contact.js

๐Ÿ›‘ Verifying Sender Identity ๐Ÿ›‘

Verify an email address or domain in the Sender Authentication Tab of SendGrid. Without this you will receive a 403 Forbidden response when trying to send mail.

Adding Env Vars

Before we get into writing any code let's open up that .env.local file and add in our API key from SendGrid. Then we're ready to spin up our API route that will actually handle sending the Email for us.
SENDGRID_API_KEY=YOUR-API-KEY-HERE We will be referencing this env variable in our API route we are creating next.

The Code

Create The API Route to Send an Email

Now that we have things set up we can get to writing some code. We'll start with the API route that will handle communicating with SendGrid. We created the file for this API route already pages/api/sendEmail.js let's open it up and get to coding. We'll take a peek at the whole API handler function and then break down a few of the pieces.

./pages/api/sendEmail.js

// ./pages/api/sendEmail.js
const sgMail = require('@sendgrid/mail');

export default async function SendEmail(req, res) {
  sgMail.setApiKey(process.env.SENDGRID_API_KEY);
  const { subject, description, email, name } = req.body;
  const referer = req.headers.referer;

  const content = {
    to: ['michael@my.email'],
    from: 'michael@my.email',
    subject: subject,
    text: description,
    html: `<div>
    <h1>Name: ${name}</h1>
    <h1>E-mail: ${email}</h1>
    <p>${description}</p>
    <p>Sent from: ${referer || 'Not specified or hidden'}`,
  };

  try {
    await sgMail.send(content);
    res.status(204).end();
  } catch (error) {
    console.log('ERROR', error);
    res.status(400).send({ message: error });
  }
}

Breaking Down the Pieces
Our content variable is where we are going to set the content for our API call to SendGrid. Inside this object we declare the "to" and "from" email addresses as well as the content of the email.

const content = {
    to: ['michael@my.email'],
    from: 'michael@my.email',
    subject: subject,
    text: description,
    html: `<div>
    <h1>Name: ${name}</h1>
    <h1>E-mail: ${email}</h1>
    <p>${description}</p>
    <p>Sent from: ${referer || 'Not specified or hidden'}`,
  };

Create The Contact Form

Now we need to create the view for the contact form. For the purpose of this blog post, we will try to keep things simple and in a single file and just do some inline styling. Let's get to it.

./pages/contact.js

// ./pages/contact.js
import {useState} from 'react';
import { useForm } from 'react-hook-form';
import Loading from '../components/Loading';

// Shows when our mail has been successfully sent  
const MailSentState = () => (
    <div>
        <h1>Success! Email has been sent!</h1>
        <h1>๐Ÿ‘</h1>
    </div>
);

// Text displayed when input has error  
const ErrorMessage = ({message}) => (
    <p
        style={{
            color: 'red',
            fontWeight: 700,
            fontSize: '.7rem'
        }}
    >
        {message}
    </p>
)

// Row containing input label and error message if any
const LabelRow = ({field, label, message, errors}) => (
    <div
        style={{
            display: 'flex',
            flexDirection: 'row',
            justifyContent: 'flex-start',
            alignItems: 'center',
            marginBottom: -15,
        }}
    >
        <p>{label}</p>
        {errors[`${field}`] &&  
            <ErrorMessage message={message}/> 
        }
    </div>
);

// Displays input with label and errors
const InputGroup = ({
    register,
    field, 
    label,
    message,
    errors,
    placeholder,
    isDisabled,
    isRequired,
    type
}) => (
    <div>
        <LabelRow 
            field={field}
            label={label}
            message={message}
            errors={errors}
        />
        {type !== 'textArea'
            ? (
                <input 
                    placeholder={placeholder}
                    id={field}
                    disabled={isDisabled}
                    {...register(field, { required: isRequired })}
                />
            )
            : (
                <textarea
                    style={{padding: '10px'}}
                    placeholder={placeholder}
                    id={field}
                    disabled={isDisabled}
                    {...register(field, { required: isRequired })}
                />
            )
        }
    </div>
);

// Our exported Contact component
export default function Contact() {
    const [loading, setLoading] = useState(false);
    const [hasSuccessfullySentMail, setHasSuccessfullySentMail] = useState(false);
    const [hasErrored, setHasErrored] = useState(false);
    const { register, handleSubmit, formState } = useForm();  
    const { isSubmitSuccessful, isSubmitting, isSubmitted, errors } = formState;

// Submit the data to SendGrid to send the email
    async function onSubmit(payload) {
        setLoading(true);
        try {
            console.log({ subject: 'Email from contact form', ...payload })
            const res = await fetch('/api/sendEmail', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({ 
                subject: 'Email from contact form', 
                ...payload 
            }),
        });
    // SendGrid only sends 204 on success - if not 204 we set error
            if (res.status !== 204) {
                setHasErrored(true);
                setLoading(false);
            }
        } catch {
            setHasErrored(true);
            setLoading(false);
            return;
        }
        setLoading(false)
        setHasSuccessfullySentMail(true);
    }

        const isSent = isSubmitSuccessful && isSubmitted;
        const isDisabled = isSubmitting || isSent;
        const isSubmitDisabled = Object.keys(errors).length > 0 || isDisabled

        if (hasSuccessfullySentMail) {
            return (
                <MailSentState />
            )
        }

        return (
            <form onSubmit={handleSubmit(onSubmit)}>
                {hasErrored && 
                    <p>Couldn&apos;t send email. Please try again.</p>
                }
                <div 
                    style={{
                        display: 'flex',
                        flexDirection: 'column',
                        padding: '10%'
                    }}
                >
                    <InputGroup 
                        errors={errors}
                        label={'Name'}
                        field={'name'}
                        message={'Name is required'}
                        placeholder="Your Name"
                        register={register}
                        disabled={isDisabled}
                        isRequired={true}
                    />
                    <InputGroup 
                        errors={errors}
                        label={'Email'}
                        field={'email'}
                        message={'Email is required'}
                        placeholder={'Your Email'}
                        register={register}
                        disabled={isDisabled}
                        isRequired={true}
                    />
                    <InputGroup 
                        errors={errors}
                        label={'Message'}
                        field={'description'}
                        message={'Message is required'}
                        register={register}
                        disabled={isDisabled}
                        isRequired={true}
                        type={'textArea'}    
                    />
                    <button
                        style={{
                            margin: '10px',
                            backgroundColor: isSubmitDisabled ? '#ccc' : 'white', 
                            cursor: isSubmitDisabled ? 'not-allowed' : 'pointer',
                            border: 'none',
                            display: 'inline-block',
                            textDecoration: 'none',
                            textAlign: 'center',
                            padding: '.75rem 1.25rem',
                            fontSize: '1.2rem',
                            color: '#000000',
                            textTransform: 'uppercase',
                            fontWeight: 700,
                            borderRadius: '0.4rem',
                            border: '2px solid',
                            cursor: 'pointer',
                        }}
                        as="button" 
                        type="submit" 
                        disabled={isSubmitDisabled}
                    >
                        {loading ? < Loading/> : 'Send Message'}
                    </button>
                </div>
                </form>
        )
}

Email Sent!

With that we should have everything we need to send form data responses as an email using NextJS and Twilio SendGrid!

As you can see, it is quite simple to send form data over email using Twilio SendGrid and NextJS! I have used this technique on my personal site micurran.dev in the past with great success.

Next Steps & Final Thoughts

Okay so now we know how to send an email containing form data from our NextJS applications. What's next, how do we make this better?
Additionally we could...

  • Redirect the user after successful submission
  • Also add user responses to a database
  • Send a separate confirmation email to the user with SendGrid

Common Issues

  • 403 Error on form submit
    You need to verify an email address or domain in the Sender Authentication Tab of SendGrid. Without this you will receive a 403 Forbidden response when trying to send mail.
  • Missing API Key
    Make sure that you have added your API key from SendGrid to your .env.local file.

Thanks for reading! ๐Ÿ™‚

Did you find this article valuable?

Support Michael Curran by becoming a sponsor. Any amount is appreciated!

Learn more about Hashnode Sponsors
ย 
Share this