Create a Newsletter app with Twitter Revue, Next.js API Routes, and Tailwindcss

Create a Newsletter app with Twitter Revue, Next.js API Routes, and Tailwindcss

Learn how to create a newsletter with Revue APIs, Next.js API routes, and Tailwindcss. The best way to include a Newsletter in your Next.js app/site.

ยท

15 min read

Featured on Hashnode

Hey there ๐Ÿ‘‹, do you like to learn from video tutorials? This article is also available as video content.

Please feel free to subscribe for the future content


Do you have an email newsletter, or consider starting one? An email newsletter gives your subscribers regular updates about your work, products, passion, life, journey, anything that you find suitable to share. We have some great vendors/products/sites that help us to instantly get started with an email newsletter service. Buttondown, Mailchimp, MailerLite, Substack are just a few to name here.

Early this year, Twitter announced the acquisition of Revue, a service that makes it free and easy for anyone to start and publish editorial newsletters. Not only that. Twitter has also made Revueโ€™s Pro features free for all accounts.

A few days back, a tweet from Revue's official account confirmed that they would allow people to subscribe to your Revue newsletter directly from your Twitter profile. Keeping some debates aside, I think it is a great move.

As the owner of a newsletter, we can promote it in many ways.

  • We can link to the newsletter page from our website, blog.
  • We can embed the subscription form to our website using simple JavaScript, HTML, CSS snippets provided by vendors.
  • Lastly, if the newsletter vendor provides APIs to access data, we can create, manage the newsletter entirely within our control. It is a powerful usage that gives your users a feeling of oneness being part of the same website, similar look-and-feel.

So, What's the Plan?

This tutorial will teach how to use the Revue APIs to fetch data into a Next.js application using the API routes(serverless functions). We will also use the tailwindcss to give the app a better look and feel.

I am on my way to migrate my old website to new website using Next.js and tailwindcss, and the newsletter will be a part of it. So, it is an excellent opportunity to share what I have implemented and learned.

TL;DR

If you want to jump into the final app or the source code early, here are the links,

Setup a Newsletter Service using Revue

To set up a newsletter with Revue, sign up to https://www.getrevue.co/ using your Twitter account or email.

Revue Sign up

Next, log in to your account to set up the newsletter by providing the name, description, layout, issues, and schedule. You can integrate many services like Twitter, Facebook, Instagram with your Revue account to fetch content from them to add to the newsletter. Additionally, you can fetch the data using the RSS feeds. You can integrate your Hshnode powered blog's RSS feed as well. I've made my wish to Sam Sycamore already ๐Ÿ˜†!

The bottom of the integration page shows your API key to access the newsletter data over HTTP requests. Please copy this key and keep it safe.

API Key

This API key will be part of the Authorization header value when using the Revue APIs. Here is the link to learn about all publicly available APIs. In this tutorial, we will use the following,

  • POST /v2/subscribers: Add a subscriber to the list.
  • GET /v2/subscribers: Returns a list of your active subscribers.
  • GET /v2/issues: Returns a list of your sent issues.

But, before that, let us build the user interface of the Newsletter Subscription app.

Build a Newsletter Subscription App using Next.js and Tailwindcss

There are plenty of starter projects available in GitHub to get started with Next.js and Tailwindcss. My personal favorite is next-starter-tailwind because of its simplicity. I'll be using it as a template to create a repository for the newsletter subscription app. Please feel free to use any other starter project you are comfortable with.

Please create a repo by clicking on the Use this template button of the next-starter-tailwind repository.

image.png

Provide required details and create a repository from the template.

image.png

Now clone the repository and browse to the project folder. Open a command prompt or terminal window to install dependencies using the following command,

npm install # Or, yarn install

At this stage, please open the project with your favorite code editor(VS Code, in my case) and make minor code changes. Open the header.js file under the components folder and find the Next.js Starter Tailwind text. Change this text to Newsletter demo powered by Next.js Revue Tailwind. Additionally, you can change the creator name, GitHub information in the footer.js file.

Now save your changes and use this command from your command prompt to launch the app.

npm run dev # Or, yarn dev

Access the app using the URL http://localhost:3000. You should see the initial user interface coming up.

Initial UI

Create the Subscription Form

Let's create a basic subscription form with a single email field and a button to subscribe. Please create a new file called Subscribe.js under the components folder with the following content.

const Subscribe = () => {
  return (
    <div className="border border-gray-200 rounded p-6 my-4 w-full bg-gray-50">
      <p className="text-gray-900 mb-6 text-lg md:text-xl">
         Want to keep your brain engaged with great UI/UX learning content?
      </p>
      <p className="text-gray-800 dark:text-gray-400 mb-10 text-base">
        Enter your email address and you'll be be added to my email newsletter, of which you can opt out any time.
      </p>
      <form className="relative my-4">
        <input
          aria-label="Email for newsletter"
          placeholder="john@email.com"
          type="email"
          autoComplete="email"
          required
          className="py-4 px-0 text-md bg-transparent w-9/12 text-gray-900 border-b-2 border-gray-600 dark:border-gray-400 dark:text-white focus:border-brand focus-visible:outline-none"
        />
        <button
          className="flex justify-center px-5 py-4 mt-8 bg-green-600 text-white font-bold text-lg"
          type="submit"
        >
          Subscribe
        </button>
      </form>

      <p className="text-xl text-gray-800 dark:text-gray-200">
        14 subscribers . 3 issues
      </p>
    </div>
  );
};

export default Subscribe;

It is a react component with a simple form having one email field and a button. We have also hardcoded the subscribers and issues count. Later, we will make the API calls to fetch those. We have styled the HTML element using tailwindcss classes.

Now move over to the index.js under the pages folder. Replace the content of the file with the following,

import Subscribe from "@components/Subscribe";

export default function IndexPage() {
  return (
    <Subscribe />
  );
}

Here we are importing and using the Subscribe component so that when the app loads, it shows the newsletter subscription form. Let's refresh the page. You should see subscription forms like,

Subscription Form

Create Next.js API Routes to Subscribe, and Many More

Now it's time to create Next.js API Routes to register a new subscriber, get the subscriber count, and list of issues.

Next.js Serverless Functions

With Next.js's API Routes, you can easily create API endpoints. In the background, it uses Node.js serverless functions. You need to create these functions inside the pages/api folder. So, let us first create a folder called api under the pages folder.

We will need the Revue API key now. Please create .env.local file at the root of the project folder with the following line,

REVUE_API_KEY=<REPLACE_THIS_WITH_REVUE_API_KEY>

Please use your API Key you have copied from the revue integration page earlier.

At this stage, you need to restart the local server for the environment variable to get loaded in our app. So stop the server and restart it using the yarn dev command.

Let's create the API route to register a new subscriber.

But, Hold On! Why Can't We use the Revue API Directly?

You can. It is possible to use the Revue APIs directly in your React components. However, there are a few advantages of using it via the Next.js APIs.

  • In the future, if you want to use another newsletter service other than Revue, your user interface component code never changes. You just change the serverless function and redeploy.
  • There is an abstraction. It helps you to deploy and host just the API separately along with your own business use cases.
  • Accessing these APIs directly on the client-side will leave you with the risk of the API key exposed that anyone can obtain easily by inspecting network requests. You do not want that!

Alright, let's move on.

Create Next.js API Route to Register a New Subscriber

Create a file called subscribe.js inside pages/api folder. It means our API route will be accessible from the UI components using the URI /api/subscribe. Please paste the following content in the subscribe.js file.

export default async function handler(req, res) {
    // 1. Get the email from the payload and
    // validate if it is empty.
    const { email } = req.body;
    if (!email) {
        return res.status(400).json({error: 'Please provide an email id.'});
    }

    // 2. Use the Revue API Key and create a subscriber using
    // the email we pass to the API. Please note, we pass the
    // API Key in the 'Authorization' header.
    try {
        const API_KEY = process.env.REVUE_API_KEY;
        const response = await fetch(
            `https://www.getrevue.co/api/v2/subscribers`,
            {
                method: 'POST',
                body: JSON.stringify({email: email, double_opt_in: false}),
                headers: {
                    'Authorization': `Token ${API_KEY}`,
                    'Content-Type': 'application/json'
                }
            }
        )

    // 3. We check in the response if the status is 400
    // If so, consider it as error and return. Otherwise a 201
    // for create        
        if (response.status >=400) {
            const message = await response.json();
            console.log(message.error.email[0]);
            return res.status(400).json({error: message.error.email[0]});
        }
        // Send a JSON response
        res.status(201).json({
            message: `Hey, ${email}, Please check your email and verify it. Can't wait to get you boarded.`,
            error: ''
        });
    } catch (err) {
    // 4. If the control goes inside the catch block
    // let us consider it as a server error(500)  
        return res.status(500).json({error: err.message || error.toString()});
    }
}

A few things are going on in the above function.

  1. When someone invokes this API function, we expect an email part of the payload. So first, get the email from the payload and validate if it is empty.
  2. Next, use the email and API_KEY to call the Revue API to register a subscriber. Note the payload here. We are passing the email value and double_opt_in value as false. In reality, you will NOT pass the double_opt_in value as false as you want your subscribers to verify email before confirming. We are doing it just for the demo's sake.
  3. Then, we check in the response if the status is 400. If so, consider it as an error and return with an error message. Otherwise, a 201 for creating and return with a success message.
  4. Last, If the control goes inside the catch block, let us consider it a server error(500).

Update the UI Code to Register Subscribers

We will update the Subscribe component to use the /api/subscribe API. Open the Subscribe.js file under the components folder and make these changes.

  1. Import the useState hook from react to manage a few states. Add this line at the top of the file.

    import { useState } from 'react';
    
  2. Create three state variables to handle the email from the user input and the error, success message from the API call. Add these three lines at the beginning of the Subscribe function as,

    const Subscribe = () => {
     const [email, setEmail] = useState('');
     const [error, setError] = useState('');
     const [success, setSuccess] = useState('');
       return (
         ..... 
         {/* Rest of the code as is */}
         ....
       )
    }
    
  3. Next, handle two events. One is to capture the user input in the email field, and the second is to handle the for submit.

    ...
    ...
    <form className="relative my-4" onSubmit={subscribeMe}>
         <input
           onChange={changeEmail}
    
  4. Now is the time to define both subscribeMe and changeEmail methods.

    const subscribeMe = async (event) => {
      event.preventDefault();
    
      const res = await fetch("/api/subscribe", {
         body: JSON.stringify({ email: email }),
         headers: { 'Content-Type': 'application/json' },
         method: "POST",
      });
    
     const { error, message } = await res.json();
      if (error) {
         setError(error);
      } else {
         setSuccess(message);
      }
    };
    
    const changeEmail = (event) => {
     const email = event.target.value;
     setEmail(email);
    }
    

    In the subscribeMe method, we call the API /api/subscribe, passing the email value as the payload. Then we handle the error and success message.

  5. Last, let's show the success and error message in the UI. Add this code right after the form element.

    {success 
           ? 
        <span className="flex items-center text-sm font-bold text-green-700"> 
             {success}
        </span> 
           : 
        <span className="flex items-center text-sm font-bold text-red-800">
              {error} 
        </span>
    }
    

    Great, now go to the app and provide an email id to register. As we have turned off the email verification, you can test it with an arbitrary email id. Please take a look into the entire source file from here.

Register Success

To verify, the email address got added successfully, got to the subscribers page of your account. You should see this new email id added,

Subascriber added

Please make sure to use the email verification by turning on double_opt_in: true in the API function for production usage.

Try the same email id again to attempt to register!

register fail

Yep, you will get that error. That's all. The subscription works well.

Get the Subscriber Count

Alright, let's get the subscriber count. So we will now write a serverless function to fetch the subscriber count. Please create a file called subscribers.js under the pages/api folder with the following content.

export default async function handler(_, res) {
  const API_KEY = process.env.REVUE_API_KEY;
  const response = await fetch('https://www.getrevue.co/api/v2/subscribers', {
    headers: {
      Authorization: `Token ${API_KEY}`,
      'Content-Type': 'application/json'
    },
    method: 'GET'
  });

  const data = await response.json();
  const count = data.length;

  res.setHeader(
    'Cache-Control',
    'public, s-maxage=1200, stale-while-revalidate=600'
  );

  return res.status(200).json({ count });
}

We use the Revue API to fetch the subscriber list and then return the count as a response. So, now we have to use the /api/subscribers URI to fetch the count. Let's do it.

Update the UI Code to Fetch Subscriber Count

We need to fetch the subscriber count when the Subscribe component loads. Also, if there is a new subscriber, we need to show the updated count in the UI. Next.js supports two kinds of pre-rendering,

  • Static Generation(SSG): In this case, everything is precompiled, prebuilt and cached. You do not see changes in your content until there is another build. It works best when you deal with static data like blog articles.
  • Server-Side Rendering(SSR): Here, the data for a page generates on demand for each request.

We prefer static generation as much as possible but may not avoid the server-side rendering in some cases. For our app, we will use SWR. As described here,

SWR is derived from stale-while-revalidate , a HTTP cache invalidation strategy popularized by HTTP RFC 5861. SWR is a strategy to first return the data from cache (stale), then send the fetch request (revalidate), and finally come with the up-to-date data.

With Next.js pre-rendering support and SWR, you can pre-render the page for SEO and allow caching, revalidation, and re-fetching at intervals on the client side.

  1. Install swr library using the command,

    npm install swr #Or, yarn add swr
    
  2. The swr library gives us a hook called useSWR. It takes two parameters, a key and a fetcher function. The key is a string value, usually the API URL that we will pass to the fetcher function, and the fetcher function can be an asynchronous function. So, let us create a simple fetcher function.

    Please create a utils folder at the root of the project and create a fetcher.js file with the following content,

    export default async function Fetcher(...args) {
     const res = await fetch(...args);
    
     return res.json();
    }
    

    Next, in the components/Subscribe.js file, include these two imports.

    import useSWR from 'swr';
    import fetcher from '../utils/fetcher';
    

    Now we can use the useSWR hook to pass the API(api/subscribers) and the fetcher function.

    const Subscribe = () => {
    
      const [email, setEmail] = useState('');
      const [error, setError] = useState('');
      const [success, setSuccess] = useState('');
      // --- above is old code ---
    
      const { data } = useSWR('/api/subscribers', fetcher);
      const subscriberCount = data?.count;
    

    Please note, we use the JavaScript optional chaining(?.) feature to get the count value. It handles the undefined value much safely.

    Every time the data gets changed at the back-end, the subscriberCount variable will have the latest count.

  3. Last is to use the subscriberCount state variable instead of the hardcoded value.

    <p className="text-sm text-gray-800 dark:text-gray-200">
      { subscriberCount } subscribers . 3 issues
    </p>
    

    That's all. Refresh the app and see the actual count reflecting.

Subscriber count

Get the Issue List

Now we need to get the issue list and the count of published issues. So we have to write a serverless function again to fetch these details. But wait, I'm not going to do that in this tutorial. Please take it as an exercise to try out.

Hint: You need to use this Revue API to fetch the data => GET /v2/issues. If you need help, the API code is here, and the component changes are here to refer to.

In the end, the UI should have the actual issue count and a list of published issues like this(I have one test issue).

final page

Let's Deploy

Congratulations!!! The app is ready to use. But, it is available only with you. Let's deploy it publicly. We will use the Vercel platform to deploy our app. It is super easy to deploy a Next.js app on Vercel using a few simple steps. To make it happen, please commit and push all your code changes to the GitHub repository.

  1. Create an account with Vercel, log in and click on the New Project button to get started.

    image.png

  2. Next, import your project from GitHub.

    image.png

  3. Now, you need to configure your project. For a Next.js project, you hardly need to make any changes to the build and other parameters. If your app is depending on any Environment Variables, you need to add them one by one. In our case, we have one. So let's add it. Then, click on the Deploy button.

    image.png

  4. Congratulations!!! You have deployed the app successfully on Vercel. Now you can access the app publicly using the URL generated by the deployment process.

    image.png

Post-deployment, you can perform many checks and additional configurations based on your needs. If your app has one or more serverless functions, you can see the live execution logs from your project's Functions tab. The image below shows the log for our functions.

image.png

In Summary

  • Next.js is the future(arguably?) for React-based projects. It is easy to set up, learn, and use. The tailwindcss is a developer-friendly CSS library to style the app. Revue is an amazing newsletter service.
  • Your users, customers like the oneness and the belongingness. Then why not get the newsletter service within the app/site itself and manage it?
  • Next.js APIs are the Node.js serverless functions in the background. It is a great way to fetch, interact with back-end services.
  • It is super easy to deploy and maintain your Next.js app(including serverless functions) using Vercel.
  • Similar to this, you can integrate many other services like GitHub, your blog, and many more that I'll cover in my upcoming post.

That's all. I hope you enjoyed building it with me. Please share/like this article and the video tutorial so that it reaches others as well.

Let's connect. Please find me on Twitter(@tapasadhikary), sharing thoughts, tips, and code practices. Would you please give a follow? You can hit the Subscribe button at the top of the page to get an email notification on my latest posts.

Did you find this article valuable?

Support Tapas Adhikary by becoming a sponsor. Any amount is appreciated!