Build a Newsletter Platform
Build a complete newsletter platform with subscriber management, email template design, sending via API, and engagement analytics.
Newsletter platform architecture
Building your own newsletter platform gives you full control over subscriber data, email design, and sending costs. Commercial platforms charge 30 to 300 dollars per month for features you can build in a weekend. The architecture has four main components: subscriber management (add, remove, segment, import/export), content creation (email editor, templates, scheduling), sending (API integration with an email service provider), and analytics (opens, clicks, unsubscribes, bounces). Ask Claude Code: Create a Next.js project with TypeScript, Tailwind, and Prisma for a newsletter platform. Define the database schema with these models. Subscriber (id, email unique, firstName, lastName, status as active or unsubscribed or bounced, subscribedAt, unsubscribedAt optional, source as where they signed up, tags as a JSON array for segmentation, metadata as a JSON object for custom fields). Newsletter (id, subject, preheaderText, htmlContent, textContent for plain text fallback, status as draft or scheduled or sending or sent, scheduledAt optional, sentAt optional, sentCount, openCount, clickCount). SendLog (id, newsletterId, subscriberId, sentAt, openedAt optional, clickedAt optional, bouncedAt optional, unsubscribedAt optional, status as sent or opened or clicked or bounced or unsubscribed). Tag (id, name, description, subscriberCount computed). Run: npx prisma migrate dev --name init. Ask Claude Code: Seed the database with 100 sample subscribers with realistic names, varied tags (early-adopter, customer, prospect, newsletter, blog), and varied statuses. This test data lets you develop the UI against realistic conditions. The email service provider handles actual delivery. Use Resend — it has a generous free tier (100 emails per day) and an excellent API. Install: npm install resend.
Subscriber management system
Subscriber management is the foundation — you need reliable tools to add, segment, and maintain your list. Ask Claude Code: Create a subscribers page at src/app/dashboard/subscribers/page.tsx. Show a data table with columns for email, name, status (with colour-coded badges: green for active, grey for unsubscribed, red for bounced), tags (as small pills), subscribed date, and actions (edit, unsubscribe, delete). Add pagination (50 per page), column sorting (click headers to sort), and a search bar that filters by email or name in real-time. Build subscriber management operations. Ask Claude Code: Create API routes for subscriber CRUD. POST /api/subscribers to add a subscriber (validate email format, check for duplicates, set status to active). PATCH /api/subscribers/:id to update subscriber details. DELETE /api/subscribers/:id to hard-delete (for GDPR compliance). POST /api/subscribers/:id/unsubscribe to change status without deleting. Add bulk operations: select multiple subscribers with checkboxes and apply actions like add tag, remove tag, unsubscribe, or delete. Add import and export. Ask Claude Code: Create a CSV import flow. Upload a CSV file, show a mapping step where the user maps CSV columns to subscriber fields (email, first name, last name, tags), show a preview of the first 10 rows, then process the import. Skip duplicates, report errors (invalid emails, missing required fields), and show a summary: 95 imported, 3 duplicates skipped, 2 errors. Add CSV export that downloads all subscribers or a filtered subset. Build a segmentation system. Ask Claude Code: Let users create segments based on rules. A segment is a saved filter: subscribers where tags contain early-adopter AND status is active AND subscribed after January 2024. Show the segment count (how many subscribers match) in real-time as rules are added. Save segments for reuse when sending newsletters. Common error: always handle the case where an email address exists but with a different status. If an unsubscribed user tries to re-subscribe, update their status back to active instead of creating a duplicate.
Email template builder
The email editor needs to produce HTML that renders correctly across dozens of email clients — Outlook, Gmail, Apple Mail, Yahoo, and mobile apps all interpret HTML differently. Ask Claude Code: Create a template system at src/lib/templates.ts. Define a base HTML email template using table-based layout (not CSS flexbox or grid — email clients do not support modern CSS). The template should include: a header with logo and newsletter name, a body container with 600px maximum width (the email standard), content blocks that can be assembled like building blocks, and a footer with unsubscribe link (legally required), physical address (required by CAN-SPAM), and social links. Create reusable content blocks. Ask Claude Code: Build these email block components: HeroBlock (large image with overlay text), TextBlock (rich text paragraph with heading), ImageBlock (full-width image with alt text and optional caption), ButtonBlock (centred call-to-action button that works in Outlook — use VML fallback), DividerBlock (horizontal rule with spacing), and TwoColumnBlock (side-by-side content using a table layout). Build the email editor page. Ask Claude Code: Create a newsletter editor at src/app/dashboard/newsletters/new/page.tsx. Show a split view: the left panel has a block palette (drag or click to add blocks) and block settings (edit the selected block's content, colours, spacing). The right panel shows a live preview of the email as it will appear. At the top, fields for Subject and Preheader Text (the preview text shown in email clients after the subject line). Add a template library. Ask Claude Code: Create 5 pre-built templates: Announcement (hero image, heading, body, CTA button), Digest (multiple story blocks with images and read-more links), Product Update (feature highlights with screenshots), Plain Text (simple text with no images — highest deliverability), and Welcome Email (personal greeting with getting-started steps). The user selects a template as a starting point and customises from there. Common error: email CSS must be inlined (style attributes on each element, not in a style tag) because many email clients strip style tags. Use a CSS inliner library before sending.
Sending system with scheduling
Sending thousands of emails reliably requires batching, rate limiting, and error handling. Ask Claude Code: Create the sending system at src/lib/send.ts. The send function takes a newsletter ID, fetches the newsletter content from the database, fetches the target subscribers (either all active subscribers or a specific segment), and sends the email to each subscriber through Resend's API. Send in batches of 50 with a 1-second delay between batches to stay within rate limits. For each subscriber, personalise the email: replace {firstName} placeholders with the subscriber's first name, add the subscriber-specific unsubscribe link (encode the subscriber ID in the URL so unsubscribe works with one click), and add tracking pixels for open tracking (a 1x1 transparent image that loads from your server, recording the open). Ask Claude Code: Create a tracking pixel endpoint at src/app/api/track/open/route.ts. When the image is requested, extract the newsletter ID and subscriber ID from the URL parameters, record the open in the SendLog table, and return a 1x1 transparent PNG. Add click tracking by rewriting links in the email to go through your server first. Ask Claude Code: Create a link rewriting function that replaces every link in the email HTML with a tracking URL like /api/track/click?nid=123&sid=456&url=encoded-original-url. The tracking endpoint records the click and redirects to the original URL. Add scheduling. Ask Claude Code: Create a scheduling system. When creating a newsletter, the user can send immediately or schedule for a specific date and time. Scheduled newsletters are stored with status scheduled and a scheduledAt timestamp. Create a cron job (or Vercel cron) that runs every minute, checks for newsletters where scheduledAt is in the past and status is scheduled, and starts the sending process. Update the status to sending during the send and sent when complete. Show a countdown timer on the dashboard for scheduled newsletters. Common error: tracking pixels are blocked by many email clients (Apple Mail, Gmail with images disabled). Open rates are therefore approximate. Click rates are more reliable — track both but trust clicks more.
Analytics and performance tracking
Newsletter analytics help you understand what content resonates and improve over time. Ask Claude Code: Create a newsletter analytics page at src/app/dashboard/newsletters/[id]/analytics/page.tsx. Show the key metrics: sent count (how many emails were sent), delivery rate (sent minus bounces, as a percentage), open rate (unique opens divided by delivered, as a percentage), click rate (unique clicks divided by delivered, as a percentage), unsubscribe rate (unsubscribes from this newsletter divided by delivered), and bounce rate (bounces divided by sent). Display these as large KPI cards at the top. Below, show an engagement timeline. Ask Claude Code: Create a chart showing opens and clicks over time since the newsletter was sent. Most engagement happens within the first 48 hours — the chart should show hourly data for the first 2 days and daily data after that. This helps you determine the best send times. Add a click map. Ask Claude Code: Create a visual click map that shows the newsletter content with click counts overlaid on each link. The most-clicked link should be highlighted. This reveals which content and which positions in the email get the most engagement — put your most important content there. Build a comparative analytics view. Ask Claude Code: Create a page that compares metrics across all sent newsletters. Show a table with each newsletter's subject line, send date, open rate, click rate, and unsubscribe rate. Sort by any column. Add sparkline charts in each row showing the engagement trend. Highlight your best and worst performing newsletters. Over time, patterns emerge: certain subject lines get higher open rates (questions, numbers, urgency), certain content types get more clicks (tutorials, case studies), and certain send times perform better. Add A/B testing support. Ask Claude Code: When creating a newsletter, let the user write two subject lines. Send version A to 10 percent of subscribers and version B to another 10 percent. After 4 hours, check which subject line had a higher open rate and send the winner to the remaining 80 percent. Record the A/B test results on the analytics page. Common error: calculating rates incorrectly. Open rate should use unique opens (count each subscriber once, even if they opened multiple times) divided by delivered count (not sent count — subtract bounces).
Compliance, unsubscribe handling, and deployment
Email compliance is legally required. CAN-SPAM (US), GDPR (EU), and CASL (Canada) all mandate specific handling of commercial email. Ask Claude Code: Implement a one-click unsubscribe flow. Add the List-Unsubscribe header to every email (required by Gmail and Yahoo as of 2024). Create an unsubscribe endpoint at src/app/api/unsubscribe/route.ts that immediately updates the subscriber status to unsubscribed without requiring login or confirmation (one-click unsubscribe is now required by major email providers). Show a simple confirmation page: You have been unsubscribed. We are sorry to see you go. Optionally allow the user to provide a reason (dropdown: too many emails, not relevant, did not sign up, other). Never send email to unsubscribed addresses — filter them out at send time and double-check in the send function. Ask Claude Code: Add a re-subscription flow. If an unsubscribed user visits your subscribe page, show a message acknowledging they previously unsubscribed and let them actively re-subscribe. This must be an explicit action, not automatic. Handle bounces. Ask Claude Code: Create a webhook handler at src/app/api/webhooks/resend/route.ts that processes Resend's delivery webhooks. When a hard bounce is received (the email address does not exist), mark the subscriber as bounced and never send to them again. When a soft bounce is received (mailbox full, server temporarily unavailable), retry once after 24 hours. After 3 soft bounces, mark as bounced. High bounce rates hurt your sender reputation, which affects deliverability to all subscribers. Deploy to Vercel with a database on Neon. Ask Claude Code: Configure the project for production. Set up environment variables for the Resend API key, database URL, and the application URL (needed for tracking pixel and unsubscribe URLs). Configure the Vercel cron for scheduled sending. Set up the Resend webhook for bounce handling. Test the complete flow: add a subscriber (use your own email), create a newsletter from a template, send it, open the email, click a link, verify open and click are tracked in the analytics, then unsubscribe and verify no further emails are received. The newsletter platform is complete — a professional-grade tool that replaces services costing 30 to 300 dollars per month with infrastructure that costs under 5 dollars per month at scale.
Email Marketing Systems
This guide is hands-on and practical. The full curriculum covers the conceptual foundations in depth with structured lessons and quizzes.
Go to lesson