🚀 Building a Newsletter Subscription Feature That Actually Works (No BS Edition)

FMFrank Mendez·
🚀 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”)

  1. User enters email → stored in DB

  2. Admin publishes a post

  3. System schedules a send (with delay)

  4. Cron job runs every minute

  5. Emails are sent automatically

  6. 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 pending sends?

  • Is scheduled_at <= now()?

If yes:

  • Mark as sending

  • Fetch 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.

💬 Leave a Comment

Want to join the conversation?