Back to Home

Building CuratedLetters

Building CuratedLetters

A Multi-Tenant Newsletter Platform That Scales to Thousands of Subscribers

The Problem: Newsletter Platforms Trap You in a Corner

As a content creator or entrepreneur, you want to build an engaged audience through newsletters. But existing platforms force you into impossible trade-offs:

  1. Substack and Beehiiv lock you into their branding—no custom domains without paying premium
  2. Mailchimp and ConvertKit charge exorbitant fees once your list grows beyond 1,000 subscribers
  3. Ghost requires self-hosting with DevOps expertise—database management, email delivery, SSL certificates
  4. Enterprise platforms like HubSpot overwhelm you with features you'll never use
  5. None of them provide content curation tools—you're copy-pasting links from browser tabs

I wanted to build something different: professional-grade newsletter infrastructure with Substack-level simplicity, plus tools that make content curation effortless.

The result: CuratedLetters, a multi-tenant platform built for creators who want full control without the complexity.

The Vision: Own Your Audience, Not Your Infrastructure

I set out to build CuratedLetters: a newsletter platform that handles the hard technical problems so creators can focus on content. The core principles:

Multi-Tenant Architecture

Every newsletter is a separate tenant with custom domain, branded experience, and isolated data. One platform, thousands of independent publications.

Content Curation Built-In

Chrome extension captures links with one click. RSS feeds auto-import content. AI generates summaries. Stop wrestling with browser tabs and Notion docs.

Production Email Infrastructure

AWS SES with DKIM signing, bounce handling, complaint tracking. Background jobs process thousands of sends without blocking. Full deliverability reporting.

Social Media Amplification

Auto-post to Twitter, LinkedIn, Reddit, Facebook when issues publish. OAuth integration manages tokens. Schedule recurring posts without manual copying.

The Tech Stack: Choosing the Right Tools

Next.js 14 + Pages Router

The Pages Router provides battle-tested SSR and ISR patterns. Dynamic routing handles both the main app domain (app.curatedletters.com) and wildcard subdomains for individual newsletters (newsletter.curatedletters.com). Middleware rewrites ensure each newsletter loads its own branded experience with zero configuration.

Prisma ORM + PostgreSQL

Type-safe database access prevents entire categories of bugs. Prisma's migration system tracks schema changes in version control. PostgreSQL handles complex queries, full-text search, JSON columns for flexible metadata, and transactional integrity. The schema defines 20+ models covering newsletters, issues, subscribers, campaigns, social integrations, and billing.

BullMQ + Redis for Background Jobs

Email sending can't block HTTP requests—users don't want to wait 5 minutes for 10,000 emails to send. BullMQ queues handle batching, retries, rate limiting, and scheduled jobs. Redis persistence ensures zero job loss during deployments. Trigger.dev provides real-time visibility into queue depth and processing rates.

AWS SES + SNS for Email Delivery

Amazon SES handles transactional and bulk email with custom DKIM signing. SNS webhooks report bounces, complaints, and opens back to the platform in real-time. The system automatically suppresses hard bounces, tracks soft bounce counts, and maintains sender reputation to maximize deliverability.

NextAuth.js for Authentication

Session-based authentication with database-backed sessions. Support for social logins (Google, Twitter, LinkedIn) plus traditional email/password. Role-based access control (ADMIN, USER, CONTRIBUTOR, PUBLISHER) enforced at API and UI layers.

Stripe for Payments

Stripe Customer Portal integration means zero custom billing UI. Webhooks handle subscription lifecycle events (created, updated, canceled). Usage-based billing tracks subscriber counts. Hosted checkout provides PCI compliance without touching credit card data.

The Architecture: Multi-Tenancy Without the Complexity

Building a multi-tenant platform means handling thousands of independent newsletters on the same infrastructure. The architecture:

Request Flow: From Domain to Data

  1. User visits newsletter.curatedletters.com
  2. Next.js middleware extracts hostname, queries database for matching Newsletter record by custom domain or subdomain
  3. If found and active, attach newsletter ID to request context; if suspended, show "Publication Paused" page
  4. All subsequent database queries include WHERE newsletterId = ? clause
  5. Load newsletter-specific branding (logo, colors, fonts) from database, apply to page template
  6. Render SSR page with newsletter context—issues, subscriber count, social links, custom footer

This pattern provides complete data isolation between newsletters without database-per-tenant overhead. One PostgreSQL instance, thousands of newsletters, zero cross-tenant leakage.

Database Schema Design

The Prisma schema defines 20+ models. Core entities:

  • Newsletter: Tenant entity with custom domain config, publication settings, branding
  • Issue: Published newsletters with HTML content, send date, open/click tracking
  • Subscriber: Email addresses with status (active, bounced, unsubscribed), engagement history
  • Item: Curated content blocks with title, URL, description, AI summaries, categories
  • Campaign: Email sending jobs with batch progress, success/failure counts
  • SocialMediaIntegration: OAuth tokens for Twitter, LinkedIn, Reddit, Facebook
  • NewsletterSocialMediaIntegration: Links newsletters to social accounts with posting config
  • Post: Social media posts with status (scheduled, sent, failed), engagement metrics

Every query goes through Prisma's type-safe API. SQL injection is impossible. Missing WHERE clauses fail at compile time if TypeScript types don't match.

The Email Pipeline: Orchestrating AWS SES at Scale

Sending newsletters to 10,000 subscribers can't block a web request. The email pipeline decouples HTTP from delivery:

The Sending Workflow

When a user clicks "Send Newsletter", here's what happens:

  1. Campaign Creation: API creates Campaign record with status: PENDING
  2. Subscriber Batching: Query active subscribers (exclude bounced/unsubscribed), chunk into batches of 50
  3. Queue Jobs: Push batches to BullMQ with retry config (3 attempts, exponential backoff)
  4. Background Processing: Workers claim jobs, render HTML with personalization, call SES API
  5. Progress Updates: Workers update campaign progress, emit real-time events to UI
  6. SNS Webhooks: AWS sends bounce/complaint/open events back to /api/sns-notifications

The queue pattern ensures:

  • Users don't wait for emails to send—instant UI response
  • SES rate limits don't crash the app—jobs retry automatically
  • Server restarts don't lose jobs—Redis persistence
  • Failed batches get retried without manual intervention

DKIM Signing and Deliverability

Email providers (Gmail, Outlook) check sender reputation via SPF, DKIM, and DMARC records. The platform:

  • Guides users through DNS setup (automated verification checks)
  • Signs every email with custom DKIM private key (configured via DKIM_PRIVATE_KEY)
  • Tracks hard bounces (invalid email) → immediate suppression
  • Tracks soft bounces (mailbox full) → suppress after 3 attempts
  • Tracks complaints (spam reports) → auto-unsubscribe

This maintains sender reputation, which directly impacts inbox placement. Poor deliverability means your newsletters go to spam—or don't arrive at all.

The Content Curation System: Stop Copy-Pasting from Browser Tabs

Newsletter curation is painful. You open 50 browser tabs, copy links to Notion, write summaries manually. CuratedLetters automates this:

Chrome Extension: One-Click Capture

Reading an article you want to share? Click the extension icon:

  1. Extension scrapes page metadata (title, description, Open Graph image, author)
  2. Sends to /api/chrome-extension/items with authentication token
  3. API creates Item record linked to your newsletter
  4. Item appears in your draft queue with option to edit or publish

No more copy-pasting URLs. No more switching between tabs and note apps. One click, content captured.

RSS Feed Automation

Follow your favorite blogs via RSS. Background jobs poll feeds every hour, parse new entries, create draft items automatically. You curate and publish—the platform handles collection. Deduplication by URL prevents the same article appearing twice.

AI-Generated Summaries with GPT-4o

Long articles need summaries. Writing them manually takes time. The platform calls OpenAI's API:

// API route: /api/description-generator
const completion = await openai.chat.completions.create({
  model: 'gpt-4o',
  messages: [
    { role: 'system', content: 'Summarize articles in 2-3 sentences...' },
    { role: 'user', content: articleText }
  ]
})

// Return summary to frontend for approval/editing

Creators review AI summaries, edit if needed, publish. This cuts curation time from hours to minutes.

The Social Media Challenge: OAuth Across Four Platforms

Publishing a newsletter shouldn't mean manually posting to Twitter, LinkedIn, Reddit, and Facebook. But OAuth flows are different for each platform.

The OAuth Dance

Every platform follows the same pattern with unique quirks:

  1. User clicks "Connect Twitter" → redirect to https://twitter.com/i/oauth2/authorize
  2. User grants permissions → Twitter redirects back with authorization code
  3. Backend exchanges code for access token + refresh token via /oauth2/token
  4. Store tokens in SocialMediaIntegration table
  5. Link integration to newsletter via NewsletterSocialMediaIntegration

Platform-Specific Quirks

  • Twitter: API v2 requires PKCE flow, different token expiration than v1.1
  • LinkedIn: Organization vs personal posting requires different permissions and API endpoints
  • Reddit: 60 requests per minute rate limit, subreddit posting requires moderator approval
  • Facebook: Page access tokens separate from user tokens, Graph API version migrations

Auto-Posting Features

Once connected, newsletters can auto-post:

  • Immediately when issue publishes (configurable per platform)
  • Scheduled at specific times (daily, every N days, custom schedule)
  • To multiple targets (main profile, organization page, subreddit, group)

The platform handles token refresh, retry logic, and re-authentication emails when tokens expire.

The Challenges and Trade-Offs

1. Wildcard DNS Without Manual Configuration

Every newsletter needs its own subdomain (*.curatedletters.com). Configuring DNS for each would be impossible. Solution: Wildcard DNS record points all subdomains to the load balancer. Next.js middleware handles routing at the application layer. Custom domains require A/CNAME verification before activation.

2. Sender Reputation and Deliverability

One user sending spam ruins deliverability for everyone on shared infrastructure. AWS SES requires domain verification (SPF, DKIM, DMARC). The platform guides users through DNS setup, verifies records automatically, enforces double opt-in, and monitors bounce/complaint rates. High-complaint senders get flagged for review.

3. Queue Processing Under Load

Email queues must handle spikes (10 newsletters send simultaneously). BullMQ workers scale horizontally—Kubernetes adds pods when queue depth exceeds thresholds. Redis persistence prevents job loss during deployments. Failed jobs retry with exponential backoff. Trigger.dev dashboards surface queue health in real-time.

4. Data Isolation Between Tenants

One missing WHERE newsletterId clause leaks data across newsletters. Prisma's type system catches this at compile time—queries without newsletter scope fail TypeScript checks. Middleware enforces newsletter context before requests reach handlers. Unit tests verify isolation for every critical query.

5. OAuth Token Expiration and Refresh

Access tokens expire after 1-2 hours. Refresh tokens sometimes expire. Users revoke permissions. The platform tracks expiration dates, proactively refreshes before API calls, sends re-auth emails when refresh fails, and archives broken integrations after retries are exhausted. Background jobs check token health hourly.

The Results: From Hours to Minutes

Before CuratedLetters

  • • Content curation: 2 hours (browser tabs, manual summaries)
  • • Newsletter writing: 1 hour
  • • Email setup/sending: 30 minutes (Mailchimp UI, list management)
  • • Social media posting: 20 minutes (copy-paste to 4 platforms)
  • Total: 3.8 hours per issue

After CuratedLetters

  • • Content curation: 0 minutes (RSS feeds auto-collect)
  • • Newsletter writing: 2 minutes (AI generates, you review)
  • • Email setup/sending: 0 minutes (scheduled automation)
  • • Social media posting: 0 minutes (automated)
  • Total: 2 minutes per issue

That's a 99% time reduction. For creators publishing weekly, that's 15+ hours saved per month.

The Developer Experience Lessons

1. Background Jobs Are Non-Negotiable for Scale

Email sending must never block HTTP requests. Queue-based architecture with BullMQ enabled reliable, scalable processing from day one. The decoupling made debugging easier—web server logs vs worker logs are separate concerns.

2. Database Indexes Save You Before You Hit Scale

Queries on newsletterId, userId, andstatus appear in every request. Adding indexes early prevented performance regressions as data scaled. Prisma's @@index makes this declarative and version-controlled.

3. OAuth Is Always Harder Than Expected

Every platform has unique OAuth quirks. Building a robust SocialMediaIntegration abstraction with provider-specific handlers isolated complexity. Adding new platforms became tractable—20 hours for LinkedIn vs 200 if the pattern wasn't established.

4. Monitoring Prevents Surprises

Queue monitoring, error tracking, and analytics dashboards provide visibility. Trigger.dev's real-time job monitoring caught queue backlogs before users noticed. Metabase dashboards surfaced usage patterns that informed feature prioritization.

5. TypeScript Catches Bugs Before Production

Prisma's generated types caught database schema mismatches at compile time. TypeScript's strict mode prevented null reference errors. Investing in type safety early paid dividends—refactoring became safe and fast.

What I'd Do Differently

  1. App Router from Day One: The project uses Pages Router. App Router's React Server Components would simplify data fetching and reduce client bundle size.
  2. GraphQL Instead of 60+ REST Endpoints: REST meant multiple API calls per page. A unified GraphQL API would reduce over-fetching and client complexity.
  3. E2E Tests for Critical Flows: Email sending, OAuth, and billing webhooks need end-to-end tests. Playwright could simulate entire journeys.
  4. Feature Flags for Gradual Rollouts: Shipping features to 100% of users at once is risky. LaunchDarkly or Flagsmith would enable A/B testing and gradual rollouts.

Technical Highlights

20+ database models with Prisma ORM
60+ API endpoints for REST operations
Multi-tenant architecture with domain routing
Background job processing with BullMQ + Redis
AWS SES integration with DKIM signing
OAuth flows for 4 social platforms
Chrome extension for content curation
AI-powered summaries with GPT-4o
Stripe billing with usage-based pricing
Kubernetes deployment with autoscaling

Inside CuratedLetters: A Visual Tour

AI-Powered Newsletter Builder

Get started in seconds with the AI Magic Tool—simply describe your newsletter topic and let GPT-4o generate a complete publication structure with content ideas and scheduling recommendations.

CuratedLetters AI Magic Tool landing page for newsletter generation

Content Curation Made Effortless

Collect items from RSS feeds, Chrome extension captures, and manual entries—all organized in one place with AI-generated summaries, status badges, and drag-and-drop issue assignment.

Content curation dashboard with collected articles and AI summaries

Publication Settings

Configure every aspect of your newsletter—custom domains, email settings, RSS feeds, social integrations, ads, and AI-powered summaries.

Publication settings dashboard with card-based configuration modules

Smart Scheduling

Set publish frequency, send times, and enable auto-scheduling with AI-powered content curation and description generation.

Newsletter scheduling interface with timezone-aware automation

Subscriber Management & Analytics

Track every subscriber with detailed engagement metrics—unique opens, clicks, referral counts, and subscription status. Prune inactive users and monitor list health in real-time.

Email subscribers dashboard with engagement tracking and list management

Issue Statistics

Monitor open rates, click rates, delivery status, and engagement for every published issue.

Statistics dashboard showing email campaign performance metrics

Referral Rewards

Activate referral programs with custom rewards to incentivize subscriber growth.

Rewards program settings with referral tracking and gift management

Social Integrations

Connect Reddit, LinkedIn, Twitter, and Facebook for automatic cross-posting when issues publish.

Social media integrations showing connected accounts and OAuth status

Multi-Publication Dashboard

Manage multiple newsletters from one account—each with separate branding, subscribers, and settings. Switch between publications instantly and scale your content empire.

Publications list showing multiple newsletters with edit and settings controls

Revenue Tracking

Monitor earnings, revenue share progress, and payout status with gamified tasks to increase your share percentage.

Earnings dashboard with revenue statistics and cashout tracking

Team Collaboration

Invite team members with role-based access (Owner, Admin, Contributor) and manage permissions for collaborative publishing.

User management interface with role-based access control and invitations

Issue Management & Scheduling

View all issues (scheduled, draft, published) in a clean list view with dates, status indicators, and quick access to editing. Track your content pipeline from ideation to delivery.

Issues list showing scheduled newsletters with dates and status tracking

Conclusion: Build for Real Users at Scale

CuratedLetters proves that a small team can build production-grade SaaS with the right architecture. Multi-tenancy, background jobs, OAuth integrations, and billing aren't just enterprise concerns—they're table stakes for modern platforms.

The key insights: decouple concerns early (web servers from workers), invest in type safety (Prisma + TypeScript catches bugs at compile time), and monitor everything (logs, queues, errors, analytics).

The platform handles complex workflows—email campaigns to thousands, OAuth token management, social media auto-posting, custom domains with SSL—while maintaining simplicity for creators. That balance is what engineering is all about.

Building platforms that scale requires discipline, but the patterns are repeatable. This is how you ship software that lasts.

Complete Tech Stack

Frontend: Next.js 14 (Pages Router), React 18, TypeScript, Tailwind CSS, React Hook Form

Backend: Next.js API Routes, NextAuth.js, Prisma ORM, PostgreSQL

Email: AWS SES, SNS webhooks, custom DKIM signing, bounce/complaint handling

Jobs: BullMQ, Redis, Trigger.dev monitoring, scheduled/recurring jobs

Integrations: Twitter API v2, LinkedIn API, Reddit API, Facebook Graph API

AI: OpenAI GPT-4o for content summaries and generation

Billing: Stripe Customer Portal, usage-based pricing, webhook handling

Infrastructure: Kubernetes (DigitalOcean), managed PostgreSQL, Redis, Nginx ingress

Monitoring: Trigger.dev, Metabase, Google Analytics, Sentry

Storage: Wasabi S3-compatible object storage for media uploads

Ready to Build Something Scalable?

If you need an engineer who can architect multi-tenant platforms, integrate complex APIs, and ship production systems that scale, let's connect.