Skip to main content
RapidDev - Software Development Agency
how-to-build-v030-60 minutes

How to Build Newsletter subscriptions with V0

Build a newsletter subscription system with V0 using Next.js, Supabase, Resend, and shadcn/ui. Features email capture with double opt-in confirmation, preference management, subscriber list admin, and seamless integration with Resend for transactional email delivery — all in about 30-60 minutes.

What you'll build

  • Email signup widget with Input validation and loading state Button
  • Double opt-in flow with confirmation token and email verification
  • Subscriber preference management page for choosing newsletter lists
  • Admin dashboard with subscriber Table, status Badges, and CSV export
  • Resend API integration for sending confirmation and welcome emails
  • Toast notifications for success and error feedback on subscription actions
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner8 min read30-60 minutesV0 FreeApril 2026RapidDev Engineering Team
TL;DR

Build a newsletter subscription system with V0 using Next.js, Supabase, Resend, and shadcn/ui. Features email capture with double opt-in confirmation, preference management, subscriber list admin, and seamless integration with Resend for transactional email delivery — all in about 30-60 minutes.

What you're building

Every startup and creator needs an email list. A double opt-in newsletter system ensures high deliverability and compliance while giving subscribers control over their preferences.

V0 makes this the perfect beginner project — describe the signup form in chat, V0 generates the full component, then use Design Mode (Option+D) to adjust colors and spacing for free. No complex integrations needed.

The architecture uses a simple API route for subscription and confirmation, Supabase for subscriber data, and Resend for transactional email delivery. The entire system can be built with the V0 free tier.

Final result

A newsletter subscription system with double opt-in email verification, preference management, admin subscriber list, and Resend email delivery.

Tech stack

V0AI Code Generator
Next.jsFull-Stack Framework
Tailwind CSSStyling
shadcn/uiComponent Library
SupabaseDatabase
ResendEmail Delivery

Prerequisites

  • A V0 account (free tier works for this project)
  • A Supabase project (free tier works — connect via V0's Connect panel)
  • A Resend account (free tier: 100 emails/day)
  • A verified domain or email address in Resend

Build steps

1

Set up the database schema for subscribers and lists

Create the Supabase schema for subscribers, newsletter lists, and list assignments. The subscribers table includes a confirmation token for double opt-in verification.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build a newsletter subscription system. Create a Supabase schema:
3// 1. subscribers: id (uuid PK), email (text UNIQUE), name (text), status (text DEFAULT 'pending' CHECK IN 'pending','active','unsubscribed'), confirmation_token (uuid DEFAULT gen_random_uuid()), preferences (jsonb DEFAULT '{}'), subscribed_at (timestamptz DEFAULT now()), confirmed_at (timestamptz)
4// 2. lists: id (uuid PK), name (text), slug (text UNIQUE), description (text)
5// 3. subscriber_lists: subscriber_id (uuid FK to subscribers), list_id (uuid FK to lists), PRIMARY KEY (subscriber_id, list_id)
6// Seed lists: 'Weekly Digest', 'Product Updates', 'Tips & Tutorials'.
7// Add RLS policies. Generate SQL and TypeScript types.

Pro tip: V0's beginner-friendly workflow makes this perfect as a first project. Describe the signup form in chat, V0 generates it, then use Design Mode (Option+D) to tweak colors for free.

Expected result: Supabase is connected with subscribers, lists, and subscriber_lists tables. Three newsletter lists are seeded.

2

Build the signup form and subscribe API

Create the landing page with an email signup widget and the API route that creates the subscriber with a pending status, generates a confirmation token, and sends the confirmation email via Resend.

app/api/subscribe/route.ts
1import { createClient } from '@supabase/supabase-js'
2import { NextRequest, NextResponse } from 'next/server'
3import { Resend } from 'resend'
4
5const supabase = createClient(
6 process.env.SUPABASE_URL!,
7 process.env.SUPABASE_SERVICE_ROLE_KEY!
8)
9const resend = new Resend(process.env.RESEND_API_KEY)
10
11export async function POST(req: NextRequest) {
12 const { email, name } = await req.json()
13
14 if (!email || !email.includes('@')) {
15 return NextResponse.json({ error: 'Invalid email' }, { status: 400 })
16 }
17
18 const { data: existing } = await supabase
19 .from('subscribers')
20 .select('id, status')
21 .eq('email', email)
22 .single()
23
24 if (existing?.status === 'active') {
25 return NextResponse.json({ error: 'Already subscribed' }, { status: 409 })
26 }
27
28 const { data: subscriber, error } = await supabase
29 .from('subscribers')
30 .upsert({ email, name, status: 'pending' })
31 .select('id, confirmation_token')
32 .single()
33
34 if (error) {
35 return NextResponse.json({ error: error.message }, { status: 500 })
36 }
37
38 const confirmUrl = `${process.env.NEXT_PUBLIC_APP_URL}/confirm?token=${subscriber.confirmation_token}`
39
40 await resend.emails.send({
41 from: 'newsletter@yourdomain.com',
42 to: email,
43 subject: 'Confirm your subscription',
44 html: `<p>Hi ${name || 'there'},</p><p>Click <a href="${confirmUrl}">here</a> to confirm your subscription.</p>`,
45 })
46
47 return NextResponse.json({ success: true })
48}

Expected result: Submitting the form creates a pending subscriber and sends a confirmation email with a unique token link.

3

Build the confirmation page and preferences manager

Create the token verification page that activates the subscription and a preferences page where subscribers manage their newsletter list selections.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Create subscription pages:
3// 1. app/confirm/page.tsx — reads 'token' from URL searchParams, calls /api/confirm to validate and activate. Shows success Card with checkmark or error Card with retry link.
4// 2. app/preferences/page.tsx — reads 'token' from URL. Shows the subscriber's current list selections as Checkbox group. Save Button updates subscriber_lists via Server Action. Add an 'Unsubscribe from all' Button with AlertDialog confirmation.
5// 3. app/api/confirm/route.ts — GET that validates the token, updates subscriber status to 'active', sets confirmed_at, nullifies the token.
6// Use shadcn/ui Card for the confirmation message, Checkbox for list selection, Toast for success feedback.

Expected result: Clicking the email link activates the subscription. The preferences page lets subscribers choose which lists they receive.

4

Build the admin subscriber dashboard and deploy

Create the admin page showing all subscribers with status filtering, export capability, and list management. Then deploy.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Create an admin dashboard at app/admin/subscribers/page.tsx.
3// Requirements:
4// - Fetch all subscribers with their list assignments
5// - Display in a shadcn/ui Table: email, name, status Badge (pending=yellow, active=green, unsubscribed=red), lists joined as Badge group, subscribed_at, confirmed_at
6// - Add filters: status Select, list Select, search Input
7// - Add 'Export CSV' Button that downloads all subscribers as a CSV file
8// - Show summary Cards: Total Subscribers, Active, Pending, Unsubscribed
9// - Add bulk actions: select rows with Checkbox, bulk delete or change status
10// - Protect this page with authentication check

Pro tip: Set RESEND_API_KEY in V0's Vars tab without NEXT_PUBLIC_ prefix since email sending is server-only. No Stripe needed for this beginner project.

Expected result: The admin dashboard shows all subscribers with filters, status badges, and CSV export. The app is deployed via Share > Publish.

Complete code

app/api/subscribe/route.ts
1import { createClient } from '@supabase/supabase-js'
2import { NextRequest, NextResponse } from 'next/server'
3import { Resend } from 'resend'
4
5const supabase = createClient(
6 process.env.SUPABASE_URL!,
7 process.env.SUPABASE_SERVICE_ROLE_KEY!
8)
9const resend = new Resend(process.env.RESEND_API_KEY)
10
11export async function POST(req: NextRequest) {
12 const { email, name } = await req.json()
13
14 if (!email?.includes('@')) {
15 return NextResponse.json({ error: 'Invalid email' }, { status: 400 })
16 }
17
18 const { data: existing } = await supabase
19 .from('subscribers')
20 .select('status')
21 .eq('email', email)
22 .single()
23
24 if (existing?.status === 'active') {
25 return NextResponse.json({ message: 'Already subscribed' })
26 }
27
28 const { data, error } = await supabase
29 .from('subscribers')
30 .upsert({ email, name, status: 'pending' })
31 .select('confirmation_token')
32 .single()
33
34 if (error) {
35 return NextResponse.json({ error: error.message }, { status: 500 })
36 }
37
38 const url = `${process.env.NEXT_PUBLIC_APP_URL}/confirm?token=${data.confirmation_token}`
39
40 await resend.emails.send({
41 from: 'newsletter@yourdomain.com',
42 to: email,
43 subject: 'Confirm your subscription',
44 html: `<p>Hi ${name || 'there'},</p>
45 <p><a href="${url}">Click here</a> to confirm.</p>`,
46 })
47
48 return NextResponse.json({ success: true })
49}

Customization ideas

Welcome email series

After confirmation, trigger a sequence of welcome emails over the first week using Vercel Cron Jobs and the Resend API.

Referral tracking

Generate unique referral links per subscriber and track how many signups each person refers for a referral rewards program.

Embed widget for external sites

Create a lightweight JavaScript embed that other websites can include to add your newsletter signup form to their pages.

Analytics dashboard

Track subscription rates, confirmation rates, and unsubscribe rates over time with Recharts line charts on the admin page.

Common pitfalls

Pitfall: Skipping double opt-in and activating subscribers immediately

How to avoid: Always implement double opt-in: insert with status 'pending', send confirmation email with a unique token, and only set status to 'active' when the token is verified.

Pitfall: Storing RESEND_API_KEY with NEXT_PUBLIC_ prefix

How to avoid: Store RESEND_API_KEY in V0's Vars tab without any prefix. Only use it in API routes (server-side).

Pitfall: Not nullifying the confirmation token after use

How to avoid: Set confirmation_token to NULL after successful confirmation. If someone visits the URL again, show a 'already confirmed' message.

Best practices

  • Always implement double opt-in for newsletter signups to ensure high deliverability and compliance
  • Store RESEND_API_KEY in V0's Vars tab without NEXT_PUBLIC_ prefix — email sending is server-only
  • Use V0's Design Mode (Option+D) to adjust signup form colors, Button styling, and Card layout without spending credits
  • Nullify the confirmation token after use to prevent replay attacks
  • Add a unique constraint on the email column to prevent duplicate subscribers
  • Include an unsubscribe link in every email that links to the preferences page with the subscriber's token

AI prompts to try

Copy these prompts to build this project faster.

ChatGPT Prompt

I'm building a newsletter subscription system with Next.js App Router, Supabase, and Resend. I need a double opt-in flow. The subscribe API should insert a subscriber with status 'pending' and a confirmation_token (UUID), then send a confirmation email via Resend with a link containing the token. The confirm API should validate the token, set status to 'active', and nullify the token. Please write both API routes.

Build Prompt

Create a beautiful email signup widget component. It should have an email Input with placeholder 'Enter your email', an optional name Input, and a 'Subscribe' Button. Show a loading spinner in the Button while submitting. On success, replace the form with a success message: 'Check your inbox to confirm.' On error, show a red Toast with the error message. Make it reusable as a component that can be placed anywhere on the site.

Frequently asked questions

What is double opt-in and why is it important?

Double opt-in means subscribers must confirm their email by clicking a link after signing up. This prevents spam signups, ensures valid email addresses, and improves deliverability. Without it, bots can flood your list with fake emails.

Can I use the free V0 plan for this project?

Yes. The newsletter system is simple enough to build with the free tier's credits. It requires just a few prompts for the signup form, confirmation page, and admin dashboard.

How much does Resend cost?

Resend's free tier allows 100 emails per day and 3,000 per month, which is plenty for a starting newsletter. Paid plans start at $20/month for higher volume.

Can subscribers manage their own preferences?

Yes. The preferences page loads with the subscriber's token from the URL and shows checkboxes for each newsletter list. Changes are saved via a Server Action. Include this URL in every email footer.

How do I export my subscriber list?

The admin dashboard has an Export CSV button that downloads all subscribers with their email, name, status, lists, and dates as a CSV file that can be imported into any email marketing tool.

How do I deploy the newsletter system?

Click Share in V0, then Publish to Production. Set RESEND_API_KEY in the Vars tab without NEXT_PUBLIC_ prefix. Set NEXT_PUBLIC_APP_URL to your production URL for correct confirmation links.

Can RapidDev help build a custom newsletter system?

Yes. RapidDev has built over 600 apps including email marketing platforms with automation sequences, A/B testing, and analytics. Book a free consultation to discuss your email strategy.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help building your app?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.