How I Integrated a Subscription Form and Database to Build My Email List

Building an email list is essential for businesses and websites because it allows you to stay in touch with your audience and keep them engaged with your brand. By collecting email addresses of your visitors, you can send them newsletters, promotional offers, and other updates directly to their inbox. This helps you establish a direct line of communication with your audience, increase website traffic, and build a loyal following of customers who are interested in what you have to offer. Additionally, having an email list is an effective way to promote your products or services and generate sales.

To do this, I created a React component called Newsletter, added it to my homepage, and integrated it with a database to store email addresses. I used Next.js and MongoDB to get this done. Here's how I did it.

React component - a simple and effective way to have an extensible subscription form on your website. This uses Tailwind CSS for styling.

import { useRef, useState } from 'react'
import { useRouter } from 'next/router'

import { Button } from './Button'

function MailIcon(props) {
 return (
   <svg
     viewBox="0 0 24 24"
     fill="none"
     strokeWidth="1.5"
     strokeLinecap="round"
     strokeLinejoin="round"
     aria-hidden="true"
     {...props}
   >
     <path
       d="M2.75 7.75a3 3 0 0 1 3-3h12.5a3 3 0 0 1 3 3v8.5a3 3 0 0 1-3 3H5.75a3 3 0 0 1-3-3v-8.5Z"
       className="fill-zinc-100 stroke-zinc-400 dark:fill-zinc-100/10 dark:stroke-zinc-500"
     />
     <path
       d="m4 6 6.024 5.479a2.915 2.915 0 0 0 3.952 0L20 6"
       className="stroke-zinc-400 dark:stroke-zinc-500"
     />
   </svg>
 )
}

function Spinner(props) {
 return (
   <div role="status">
     <svg
       aria-hidden="true"
       class="mr-2 h-8 w-8 animate-spin fill-blue-600 text-gray-200 dark:text-gray-600"
       viewBox="0 0 100 101"
       fill="none"
       xmlns="http://www.w3.org/2000/svg"
     >
       <path
         d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
         fill="currentColor"
       />
       <path
         d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
         fill="currentFill"
       />
     </svg>
     <span class="sr-only">Loading...</span>
   </div>
 )
}

export function Newsletter() {
 const emailInputRef = useRef(null)
 const router = useRouter()
 const [isLoading, setIsLoading] = useState(false)

 function registrationHandler(event) {
   event.preventDefault()

   const enteredEmail = emailInputRef.current.value
   setIsLoading(true)

   fetch('/api/newsletter', {
     method: 'POST',
     body: JSON.stringify({ email: enteredEmail }),
     headers: {
       'Content-Type': 'application/json',
     },
   })
     .then((response) => {
       if (response.ok) {
         return response.json()
       }
       return response.json().then((data) => {
         console.log(data)
         throw new Error(data.message || 'Something went wrong!')
       })
     })
     .then((data) => {
       console.log('Successfully registered email!')
     })
     .catch((error) => {
       console.log('Error registering email!')
       console.log(error)
     })
     .finally(() => {
       setIsLoading(false)
       router.push('/thank-you')
     })
 }

 return (
   <form
     action="/thank-you"
     onSubmit={registrationHandler}
     className="rounded-2xl border border-zinc-100 p-6 dark:border-zinc-700/40"
   >
     <h2 className="flex text-sm font-semibold text-zinc-900 dark:text-zinc-100">
       <MailIcon className="h-6 w-6 flex-none" />
       <span className="ml-3">Stay up to date</span>
     </h2>
     <p className="mt-2 text-sm text-zinc-600 dark:text-zinc-400">
       Get notified when I publish something new, and unsubscribe at any time.
     </p>
     <div className="mt-6 flex">
       <input
         type="email"
         placeholder="Email address"
         aria-label="Email address"
         required
         className="min-w-0 flex-auto appearance-none rounded-md border border-zinc-900/10 bg-white px-3 py-[calc(theme(spacing.2)-1px)] shadow-md shadow-zinc-800/5 placeholder:text-zinc-400 focus:border-teal-500 focus:outline-none focus:ring-4 focus:ring-teal-500/10 dark:border-zinc-700 dark:bg-zinc-700/[0.15] dark:text-zinc-200 dark:placeholder:text-zinc-500 dark:focus:border-teal-400 dark:focus:ring-teal-400/10 sm:text-sm"
         ref={emailInputRef}
       />
       <Button type="submit" className="ml-4 flex-none">
         {isLoading ? <Spinner /> : 'Join'}
       </Button>
     </div>
   </form>
 )
}

  • Uses the useState and useRef hooks to manage the state and to get the value from the input field.
  • Uses the useRouter hook to get the current route.
  • Renders an SVG icon, an input field for the email, and a button.
  • Calls the registrationHandler function when the user clicks the button. It gets the entered email value from the input field, sets the isLoading state to true, and sends a POST request to the /api/newsletter endpoint with the entered email value as the request body.
  • Displays a success message if the response is successful, or an error message if the response is unsuccessful. It also logs the response data or error message respectively.
  • Displays a spinner during the fetch request to indicate that the request is being processed.

I then imported this component into my index.js file and rendered it in the Newsletter section.

import { Newsletter } from '../components/newsletter'

function HomePage() {
  return (
    <div>
      // Other code here
      <Newsletter />
      // Other code here
    </div>
  );
}

export default HomePage;

I also configured the API route to handle the POST request. I created a new file in the pages/api directory called newsletter.js and added the following code to it.

import { connectDatabase, insertDocument } from '@/lib/db-utils'

export default async function handler(req, res) {
  if (req.method === 'POST') {
    const userEmail = req.body.email

    if (!userEmail || !userEmail.includes('@')) {
      res.status(422).json({ message: 'Invalid email address.' })
      return
    }

    let client

    try {
      client = await connectDatabase()
    } catch (error) {
      res.status(500).json({ message: 'Connecting to the database failed!' })
      return
    }

    try {
      await insertDocument(client, 'newsletter', { email: userEmail })
      client.close()
    } catch (error) {
      res.status(500).json({ message: 'Inserting data failed!' })
      return
    }

    res.status(201).json({ message: 'Signed up!' })
  }
}
  • Checks if the request method is POST. If it is not, it returns a 405 status code and a message.
  • Checks if the email address is valid. If it is not, it returns a 422 status code and a message.
  • Connects to the database and inserts the email address into the newsletter collection. If the connection fails, it returns a 500 status code and a message. If the insertion fails, it also returns a 500 status code and a message.
  • Returns a 201 status code and a message if the insertion is successful.

To help keep the code clean, I created a new directory called lib in the root directory of the project and added a new file called db-utils.js to it to handle the database connection. I then added the following code to it.

import { MongoClient } from 'mongodb'

export async function connectDatabase() {
  const client = await MongoClient.connect(process.env.MONGODB_URI)
  return client
}

export async function insertDocument(client, collection, document) {
  const db = client.db()
  const result = await db.collection(collection).insertOne(document)
  return result
}

And thats it! I now have a fully functional newsletter subscription form that sends the email address to my MongoDB database. I can now use these email address to send newsletters to my subscribers.