This tutorial only walks you through how to implement code, not purchasing domain or adding DNS.
When I first planned to build this blog, I was going to have both a frontend and a backend, meaning a separate server. But I realized that would be over engineering for the amount of traffic this blog was actually going to get.
Once more people start coming to my blog, I might consider migrating everything to a setup with a dedicated server. For now, though, Next.js as my frontend framework has been working perfectly fine, with one exception.
How should I implement subscriptions and email notifications on this site?
After all, this is a blog, so having a subscription feature felt like a must.
To be fair, it's not technically "serverless" since I'm using Next.js (with React Router under the hood) as both my frontend and my server. Everything lives in a single folder called frontend, and Next.js handles the server side through its API routes.
You get the idea though: no separate backend service.
Since React already provides so many tools a developer can use within the same environment, there’s a solution for this too: React Email.
What is React Email?
React Email is an open source library that lets you build and style email templates using React components instead of writing raw HTML and inline CSS by hand.
It renders those components into email safe HTML, so your templates stay consistent across different email clients.
For sending the actual emails, I used Resend, but you could just as easily use another provider such as SendGrid or Postmark.
For this project, I went with Resend mainly because it offers a generous free tier for email delivery, and it integrates cleanly with React Email.
Resend Set Up Tutorial
For validation, I used a library called Zod.
What is Zod?
Zod is a TypeScript first schema validation library that lets you define a schema once and use it to both validate data at runtime and infer static types automatically.
It's commonly used to validate form inputs, API request bodies, and environment variables.
You don't have to use it specifically for email subscriptions. It works well for pretty much any validation case, and it's especially handy when Next.js is doing double duty as your only server.
Lastly, Sonner handles the toast notifications that pop up after a user clicks the "Subscribe" button, letting them know whether the subscription succeeded or whether they're already on the list.
To sum up, here's the stack we'll be using to implement email subscriptions in a Next.js only environment.
- Database / ORM: PostgreSQL + Prisma 6.19
- Email sending: Resend (
resend@6.12) for delivery, React Email (react-email@6.0) for writing templates in JSX - Validation: Zod 4.4
- UI: Next.js 16 App Router, with Sonner for toast alerts
Let’s take a look at the whole flow first and put things together.

1. Build Your UI
This step could really go first or last, since order doesn't matter much here. All you need is a form and a button that says "Subscribe." I built mine first, so let's take a look at how that turned out.
frontend/components/ui/SubscribeBar.jsx,

2. Connect the Database
I used PostgreSQL for this project, along with Prisma as the ORM. Prisma isn't required here. I mainly wanted an excuse to try it out, so this was more of a personal choice than a technical necessity. I'll go into more detail on Prisma in a bit.
frontend/lib/prisma.ts - Prisma client

frontend/docker-compose.yml - local PostgreSQL
I ran PostgreSQL locally through Docker for development, but if you're deploying this for real, you'll want a hosted database instead.
I'm leaning toward Neon for this project, but I haven't fully committed yet, so running it locally has been my temporary choice for now.

+
In the end, I went with Neon through Vercel's native integration. Vercel's Storage tab lets you provision a Neon Postgres database directly from your project dashboard, and I've been really happy with having that visibility built right in.

To switch to Neon, you'll need to update your DATABASE_URL environment variable to point to your Neon connection string, then follow the setup steps shown on Vercel's integration page to install the required dependencies.

3. Set Up Environment Variables
Before wiring up the API, you need a place to store your database URL and your Resend API key.
These live in .env / .env.local, which are excluded from the repo through .gitignore, so you won't find the actual values there. If you're cloning this project yourself, check whether an .env.example file exists and use it as a reference for which variables you need to fill in.
.env / .env.local

4. API Routing
This is the file where input validation happens. You can see exactly what I'm validating in the code below.
frontend/app/api/subscribers/route.js,

frontend/lib/validations/validations.js

5. Check for Duplicate Emails
Before inserting a new subscriber, the API checks whether that email already exists using prisma.subscriber.findUnique. This is what lets the app tell someone "you're already subscribed" instead of silently creating a duplicate row, or worse, sending them the same welcome email twice.
frontend/app/api/subscribers/route.js (inside the same file, in the prisma.subscriber.findUnique block)

6. Email Service Layer
This is where the actual email sending logic lives.
frontend/lib/service/email-service.js

- Imports the Resend client, the email log saver, and three email templates (Welcome, Estimate, Admin Notification)
- Reads the sender address and admin address from environment variables
sendWelcomeEmail: sends a welcome email to a new subscribersendEstimateEmail: sends an estimate summary email to the person who requested itsendAdminEstimateNotification: notifies the admin when a new estimate request comes in- Every function follows the same pattern: send through Resend, log the result as
SENTorFAILED, and return{ success, data or error } - Both Resend level errors and thrown exceptions are caught and logged separately
frontend/lib/resend.js

7. Log Email Activity
Once an email actually goes out, it's worth keeping a record of it. This step logs each send so you can later check whether a welcome email went through, failed, or was never triggered in the first place.
frontend/lib/repository/email-log.js

8. Data Model
Next.js + PostgreSQL + Prisma
What is Prisma?
Prisma is a type safe ORM for Node.js and TypeScript that generates a fully typed client based on your database schema, so you get autocomplete and compile time checks for every query you write.
What makes this stack work well together is that each piece covers a different layer cleanly: Next.js handles both the frontend and the API routes, PostgreSQL provides a reliable relational database, and Prisma bridges the two with type safe queries and simple migrations.
The reason to use all three together, rather than just Next.js and PostgreSQL, comes down to developer experience. Without Prisma, you'd be writing raw SQL or juggling a lower level client by hand, and you'd lose the automatic type safety that keeps your queries and your schema in sync as the project grows.
frontend/prisma/schema.prisma

You can simply type npx prisma studio on the terminal to open up their UI page.


If you want to read 5 minutes long article about Prisma, you can check out my blog post here.
9. Email Template
This is where React Email comes back into play, letting you build the actual email template you'll send to your subscribers.
frontend/emails/WelcomeEmail.jsx

10. Test It Out!
1. Hit subscribe from the deployed (or local) environment

2. Sonner displays a toast alert

3. Check the data on Prisma Studio and your actual db


4. Check your Email Inbox

+ You can also check your choice of email Api provider’s log or email history.


