Revue alternative for your personal blog - MailChimp

Last year, I wrote an article (in Chinese) about how to create a newsletter with Next.js and Revue. But unfortunately, Revue was shut down by Elon Musk in this early year.

After trying SubstckConvertKitmailerlite and MailChimp, MailChimp won for me because:

  • The free plan, which is appropriate for a personal startup, allows up to 500 contacts and 2,500 monthly sends.
  • Even if you're on the free plan, it offers APIs. If you already have a personal website, you won't need another one to store your newsletters.

Setting up MailChimp#

After creating a free account,you'll need to fetch your API key。And other values you'll need to grab:

  • The API server region. Log into your Mailchimp account and look at the URL in your browser. You'll see something like https://us19.admin.mailchimp.com/; the us19 part is the server region.
  • The Audience ID for the mailing list.

Add Secrets to Environment Variables#

Let's keep your secrets in environment variables without hardcoding them and revealing them in the public. If you are using Next.js and deploying with Vercel,you can:

MAILCHIMP_API_DC=usxx
MAILCHIMP_API_KEY=dbxxxxxxxxxxxxxxxxxxx-usxx
MAILCHIMP_API_AUDIENCE_ID=30xxxxxx

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

Creating The Request for Subscription#

We can create an API route at pages/api/subscribe.js to add a member to our list.

import type { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { email } = req.body;

  res.setHeader('Access-Control-Allow-Origin', '*');

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

  // https://mailchimp.com/developer/marketing/api/list-members/
  const revRes = await fetch(
    `https://${process.env.MAILCHIMP_API_DC}.api.mailchimp.com/3.0/lists/${process.env.MAILCHIMP_API_AUDIENCE_ID}/members`,
    {
      method: 'POST',
      headers: {
        Authorization: `anystring ${process.env.MAILCHIMP_API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        email_address: email,
        status: 'subscribed',
        tags: ['xxx'] // If you wanna add some tags
      })
    }
  );

  const data = await revRes.json();

  if (data.status >= 400) {
    return res.status(500).json({
      error: data?.detail ?? 'Something went wrong'
    });
  }

  return res.status(200).json({ error: '' });
}

Creating A Subscribe Component#

We need a component to send a request to our API, for gathering user's email.

import Image from 'next/image';
import React, { FormEvent, useRef, useState } from 'react';

const LoadingSVG = () => {
  return (
    <div>
      <svg
        xmlns="http://www.w3.org/2000/svg"
        viewBox="0 0 19 17"
        className="fill-white dark:fill-white"
        width="20"
        height="20">
        <circle className="loadingCircle" cx="2.2" cy="10" r="1.6" />
        <circle className="loadingCircle" cx="9.5" cy="10" r="1.6" />
        <circle className="loadingCircle" cx="16.8" cy="10" r="1.6" />
      </svg>
    </div>
  );
};

const Subscribe = () => {
  const inputEl = useRef<HTMLInputElement>(null);
  const [errMsg, setErrMsg] = useState('');
  const [loading, setLoading] = useState(false);

  const subscribe = async (e: FormEvent) => {
    e.preventDefault();
    setLoading(true);

    const res = await fetch('/api/subscribe', {
      body: JSON.stringify({
        email: inputEl?.current?.value
      }),
      headers: {
        'Content-Type': 'application/json'
      },
      method: 'POST'
    });

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

    if (error) {
      setErrMsg(error);
      return;
    }

    if (inputEl?.current?.value) {
      inputEl.current.value = '';
    }

    setErrMsg('Success! 🎉 You are now subscribed to the newsletter.');
  };

  return (
    <div className="w-full p-4 mt-10 fontOutfit">
      <div className="flex justify-between items-center">
        <div>
          <Image
            className="w-8 h-8 rounded-full overflow-hidden"
            src="/portrait/logo.jpg"
            alt="portrait"
            width="100px"
            height="100px"
          />
        </div>

        <div className="max-w-[55ch]">
          <p className="font-medium text-sm">Have a weekly visit of</p>
          <p className="font-bold text-2xl bg-gradient-to-r from-subscribleRight to-subscrible bg-clip-text text-transparent">
            Howl&apos;s Moving Castle
          </p>
          <p className="font-light mt-4">
            Get emails from me about web development, tech, and early access to new articles. I will
            only send emails when new content is posted.
          </p>
          <p className="font-bold">Subscribe Now!</p>
        </div>
      </div>
      <div>
        <form className="relative mt-4 border rounded" onSubmit={subscribe}>
          <input
            className="px-4 py-2 block w-full border-gray-300 rounded-md bg-white dark:bg-bg text-gray-900 dark:text-gray-100 pr-32 outline-none"
            ref={inputEl}
            placeholder="Your e-mail address"
            type="email"
            autoComplete="email"
            required
          />
          <button
            type="submit"
            className="bg-gradient-to-r from-subscribleRight to-subscrible flex items-center justify-center absolute right-1 top-1 px-4 py-1 font-medium bg-subscrible text-white rounded w-28 h-9">
            {loading ? <LoadingSVG /> : 'SUBSCRIBE'}
          </button>
        </form>
        {errMsg && <div className="mt-4 text-gray-500 text-sm">{errMsg}</div>}
      </div>
    </div>
  );
};

export default Subscribe;

We end up with a scene that like the one below:

Cheese 🧀#

We can embed Mailchimp's campaigns into our individual websites. Dive into the entire souce code for my blog is open source.

portrait

Have a weekly visit of

Howl's Moving Castle

Get emails from me about web development, tech, and early access to new articles. I will only send emails when new content is posted.

Subscribe Now!