š Building a Newsletter Subscription Feature That Actually Works (No BS Edition)
No Mailchimp. No overengineering. Just a clean, event-driven newsletter system that works.
The Problem With Most āNewsletter Featuresā
Letās be honestāmost newsletter setups are either:
Overkill (hello 20-step Mailchimp workflows)
Manual (someone forgets to hit āsendā)
Or duct-taped together like a weekend hackathon project
What we actually want is simple:
User subscribes ā You publish ā Email goes out automatically
Thatās it. No ceremony.
š§ The Approach: Keep It Lean
This feature was built into a Blog CMS with a few strict rules:
No new vendors (use existing stack)
No double opt-in friction
Fully automated sending
One-click unsubscribe (because lawsuits are expensive š )
š Full design spec:
āļø Core Architecture
Instead of relying on external newsletter platforms, this setup runs fully in-house:
Database: Supabase
Email: Resend
Scheduler: Vercel Cron
Frontend: Next.js
Flow (a.k.a. āwhat actually happensā)
User enters email ā stored in DB
Admin publishes a post
System schedules a send (with delay)
Cron job runs every minute
Emails are sent automatically
User can unsubscribe with one click
No dashboards. No manual triggers. No āoops we forgot to send.ā
šļø Data Model (Simple but Powerful)
Two tables. Thatās it.
1. newsletter_subscriptions
Tracks subscribers:
email (unique)
subscribed_at
unsubscribed_at (null = active)
unsubscribe_token (for one-click unsubscribe)
2. newsletter_sends
Acts like a queue:
post_id (1 send per post)
scheduled_at
status (pending ā sending ā sent/failed)
This separation is key. It lets you:
Schedule emails
Retry failures
Track delivery
ā±ļø The Secret Sauce: Delayed Sending
Instead of blasting emails immediately:
NEWSLETTER_DELAY_MINUTES=60
Why this matters:
Gives you time to fix mistakes after publishing
Avoids āoops typo in productionā emails
Feels more intentional
š Automation via Cron
A Vercel cron job runs every minute:
* * * * * ā /api/newsletter/cron
It checks:
Any
pendingsends?Is
scheduled_at <= now()?
If yes:
Mark as
sendingFetch active subscribers
Send emails
Update status
If something crashes midway?
š Anything stuck in sending for 10+ minutes is marked as failed
No silent failures. No ghost jobs.
āļø Subscription Experience (UX Matters)
The Subscribe Form
Placed at the bottom of every blog post:
Email input
Subscribe button
Instant feedback (success or error)
No confirmation email. No friction.
Because letās be realāevery extra step kills conversions.
ā Unsubscribe (Donāt Mess This Up)
Every email includes:
/api/newsletter/unsubscribe?token=xxx
Click ā done.
No login. No āare you sure?ā guilt trips.
Just clean, respectful UX.
š”ļø Edge Cases Youāll Actually Hit
Handled upfront:
Scenario | Result |
|---|---|
Duplicate email | āAlready subscribedā |
Re-subscribe | Reactivates user |
Invalid token | 404 |
No subscribers | Send marked as complete |
Cron crash | Auto-mark failed |
This is where most systems break. Donāt skip it.
š§Ŗ Testing Strategy
Because āit works on my machineā isnāt a strategy:
Unit tests: subscribe/unsubscribe logic
Integration tests: cron + email dispatch
E2E tests: full user flow
Yes, even for a āsimpleā feature.
š” Why This Approach Wins
Letās compare:
Approach | Reality |
|---|---|
Mailchimp | Expensive + overkill |
Manual send | Someone forgets |
Zapier hacks | Fragile |
This system | Clean, automatic, predictable |
You control everything. No black boxes.
š§ Final Thoughts
This isnāt just a newsletter featureāitās an event-driven system disguised as one.
Publish event ā triggers queue
Queue ā processed by cron
State machine ā tracks delivery
Itās simple on the surface, but solid under the hood.
And thatās the sweet spot.
š„ If Youāre Building Something Similarā¦
Start here:
Keep your data model clean
Automate everything
Design for failure (because it will happen)
Avoid adding tools just because they exist
š TL;DR
If your newsletter requires manual effort, itās broken.
Automate it. Keep it lean. Ship it.
Stay in the loop
Get notified when new posts are published. No spam, unsubscribe anytime.
No spam Ā· Unsubscribe anytime