Create A Newsletter with Next.js API Routes and Mailchimp

November 10, 2019 (5y ago)

You've probably noticed the rise of newsletters (especially in the developer community). They're an excellent way to promote content to those who really want to read it. If you've ever thought about starting a newsletter, then you've found the right article.

Mailchimp vs. The World

If you're just starting a newsletter, you probably want something with a free tier. That was my rationale for adopting TinyLetter. It was simple, easy to set up, and didn't require an API route. Perfect!

However, it wasn't exactly a frictionless sign-up process. When a user clicked "subscribe", it launched a pop-up window where they had to confirm their email address again. Again, it works, but we can do better.

I started to explore using Mailchimp as an alternative. It also has a free tier if you have less than 2,000 subscribers. Perfect.

Why Next.js?

Next.js is the easiest way to build applications in React. One of my favorite features is API routes.

API routes provide a solution for building a complete API inside Next.js. All you need to get started is an api/ folder inside your main pages/ folder where your routes live.

Every file inside pages/api/ is mapped to /api/*. This is where we'll communicate with Mailchimp. First, we need to set up an account.

Setting up Mailchimp

After creating an account, you'll need to fetch your API key.

When a user clicks "subscribe", we'll want to add their email address to our audience. We'll need to grab a few values:

Environment Variables

Let's make our secrets we've retrieved available without hardcoding them into our request. Since I'm deploying with Vercel, I'll need to set up some environment variables. First, let's create .env.local for testing locally.

MAILCHIMP_API_KEY=
MAILCHIMP_API_SERVER=
MAILCHIMP_AUDIENCE_ID=

Finally, add these environment variables in Vercel so they're available on pull requests (Preview) and in Production.

Note: Don't forget to add .env.local to your .gitignore. We don't want to commit our secrets.

Creating The Request

Now that we have our values available as environment variables, we can create an API route at pages/api/subscribe.js to add a member to our list. Make sure you install the @mailchimp/mailchimp_marketing library.

import mailchimp from '@mailchimp/mailchimp_marketing';

mailchimp.setConfig({
  apiKey: process.env.MAILCHIMP_API_KEY,
  server: process.env.MAILCHIMP_API_SERVER, // e.g. us1
});

export default async (req, res) => {
  const { email } = req.body;

  if (!email) {
    return res.status(400).json({ error: 'Email is required' });
  }

  try {
    await mailchimp.lists.addListMember(process.env.MAILCHIMP_AUDIENCE_ID, {
      email_address: email,
      status: 'subscribed',
    });

    return res.status(201).json({ error: '' });
  } catch (error) {
    return res.status(500).json({ error: error.message || error.toString() });
  }
};

Creating A Form Input

Now that our API is created, we need a way to gather user input. Let's create a component to send a request to our API.

import React, { useRef, useState } from 'react';

function Subscribe() {
  // 1. Create a reference to the input so we can fetch/clear it's value.
  const inputEl = useRef(null);
  // 2. Hold a message in state to handle the response from our API.
  const [message, setMessage] = useState('');

  const subscribe = async (e) => {
    e.preventDefault();

    // 3. Send a request to our API with the user's email address.
    const res = await fetch('/api/subscribe', {
      body: JSON.stringify({
        email: inputEl.current.value,
      }),
      headers: {
        'Content-Type': 'application/json',
      },
      method: 'POST',
    });

    const { error } = await res.json();

    if (error) {
      // 4. If there was an error, update the message in state.
      setMessage(error);

      return;
    }

    // 5. Clear the input value and show a success message.
    inputEl.current.value = '';
    setMessage('Success! 🎉 You are now subscribed to the newsletter.');
  };

  return (
    <form onSubmit={subscribe}>
      <label htmlFor="email-input">{'Email Address'}</label>
      <input
        id="email-input"
        name="email"
        placeholder="you@awesome.com"
        ref={inputEl}
        required
        type="email"
      />
      <div>
        {message
          ? message
          : `I'll only send emails when new content is posted. No spam.`}
      </div>
      <button type="submit">{' Subscribe 💌'}</button>
    </form>
  );
}

Conclusion

If you'd like to see a completed example, the entire source code for my blog is open source.