In this blog, we will build a newsletter app where users will be able to subscribe and select how often they want to recieve their newsletters. We will use Upstash Redis to store the subscription data and Upstash Workflow to manage the actions of storing data, sending welcome emails, and scheduling newsletters based on the user's preferences.
Motivation
First of all, serverless environments are great! They are highly scalable and easy on the budget. However, they come with certain limitations, such as execution time limits. This can especially be problematic when you need to run long-running tasks.
That's where Upstash Workflow comes into play. With Upstash Workflow, you can create persistent workflows that can run as long as they need to. So, you don't have to worry about serverless function timeouts anymore.
Here is a list of features that you get when using Upstash Workflow:
No more serverless function timeouts: Your workflows can run as long as they need to.
Automatic recovery: If something goes wrong and a workflow fails midway, it automatically recovers.
Automatic retries: If any step in the workflow fails, it will be retried automatically.
Real-time monitoring: You can monitor your workflows in real-time from the Upstash Console.
Prerequisites
Basic understanding of Next.js applications.
An Upstash account for Redis and QStash tokens.
Vercel account for deployment.
ngrok (recommended) for local development.
Project Setup
Let's start by bootstrapping a new Next.js project using create-next-app:
Now, let's add the necessary dependencies to interact with Upstash QStash and Redis services:
Directory Structure
Before diving into the code, let's take a quick look at how we'll organize our project:
src/app/: This is where our main application components and pages will live.
src/app/api/: We'll put our API routes here—for subscribing, unsubscribing, and handling workflows.
src/components/: This folder will contain our subscription and unsubscription form components.
src/lib/: Utility functions for Redis and email sending will go here.
src/types/: We'll keep our TypeScript type definitions in this directory.
Environment Variables
We need to create a .env file at the root of our project and add the following:
QSTASH_TOKEN: Our Upstash QStash token accessed from the Upstash Console.
UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN: Our Upstash Redis credentials accessed from the Upstash Console.
EMAIL_SERVICE_URL: The endpoint of our email sending API.
NEXT_PUBLIC_BASE_URL: The base URL of our deployed application (e.g., https://your-app.vercel.app).
We can also set the UPSTASH_WORKFLOW_URL variable in our .env file for local development with our ngrok URL. To learn more about how to develop workflows locally with ngrok, refer to the Upstash Documentation.
The UPSTASH_WORKFLOW_URL environment variable is only necessary for local development. In production, the baseUrl parameter is automatically set and can be omitted.
Project Implementation
Subscription Form Component
The SubscriptionForm component allows users to enter their email and select how often they want to receive the newsletter. When the form is submitted, we send a POST request to /api/subscribe with the form data.
Unsubscribe Form Component
The UnsubscribeForm component allows users to enter their email to unsubscribe from the newsletter. When the form is submitted, we send a POST request to /api/unsubscribe with the email data. It also pre-fills the email field if the user clicked an unsubscribe link in one of the emails.
Store Data in Redis
We'll use Upstash Redis to store user subscription data.
In order to use Upsatsh Redis, we first need to set up a Redis database on Upstash Console and get our REST URL and token. For more information on this, you can check out the Upstash Documentation.
redis.ts will contain our Redis client and helper functions for interacting with Redis:
We also need a type definition for the subscription data:
Subscribe API Route
We'll create an API route that handles subscription requests. When a user submits the subscription form, this endpoint will check if the user is already subscribed and enqueue a workflow to handle sending emails based on the user's chosen frequency.
Unsubscribe API Route
Since we have a subscription route, we need an unsubscribe route as well. When a request is made, we'll check if the user is subscribed and remove their data from Redis. We'll also send a confirmation email.
Workflow API Route
Now, here's the fun part! We'll create an API route that handles the workflow for sending newsletters at the specified frequency intervals.
Our workflow will do the following:
Store the user's subscription data in Redis.
Send a welcome email.
Enter a loop:
Wait for the specified frequency duration.
Check if the user is still subscribed.
Send the newsletter email.
Repeat until a set number of newsletters have been sent since we don't want an infinite loop.
Here's an example of a completed workflow for a user who subscribed, received a single newsletter, and unsubscribed:
You can access and monitor your workflows from the Upstash Console.
Main Page Component
Let's set up the main page of our application. This page will include the subscription form and a link to the unsubscribe page.
Unsubscribe Page Component
Finally, let's create the unsubscription page.
Conclusion
And there you have it! We've built a simple newsletter app without worrying about serverless function timeouts.
You can find the complete source code for this project on GitHub and you can check out the live demo here.
For more information on Upstash Workflow, you can refer to the Upstash Documentation.
If you have any questions, feel free to reach out to us on Discord. Also, don't forget to explore the Upstash Blog for more tutorials and use cases.