Distro — AI-Powered Content Distribution for Startups

Inspiration

As a startup founder, I noticed a painful pattern: every time we had something to announce — a milestone, a feature launch, a partnership — we'd spend hours manually crafting posts for LinkedIn, Twitter, Reddit, email, Instagram, TikTok, YouTube, and Facebook. Each platform has its own tone, format, and character limits. Most startups either ignore half the channels or post the same generic text everywhere.

I wanted to build a tool that gives a two-person startup the distribution power of a full marketing team. One brief in, eight platform-native posts out — each adapted to the channel's culture and constraints.


What It Does

Distro takes a single content brief (e.g., "We just hit 1,000 customers — share the milestone authentically") and a voice profile, then uses Claude's API to generate tailored content for 8 platforms simultaneously:

  • Text platforms: LinkedIn, X/Twitter, Reddit, Facebook, Email
  • Video platforms: YouTube, Instagram Reels, TikTok

Each platform gets its own detail page where you can edit content, upload media, preview how it'll look, and post — either manually or on autopilot. A recurring calendar scheduler supports default brand promotion briefs with per-day campaign overrides.


How I Built It

Stack

  • Frontend: Next.js 14 (App Router), TypeScript, Tailwind CSS
  • AI: Anthropic SDK with Claude Sonnet 4 (claude-sonnet-4-20250514)
  • Integrations: LinkedIn OAuth, Twitter OAuth 1.0a, Reddit OAuth, Resend (email)
  • State: Client-side persistence via localStorage

Architecture

The core generation pipeline works as follows:

  1. User submits a brief $B and voice profile $V$
  2. The API constructs a structured prompt $P(B, V, \text{{platform}})$ for each platform
  3. Claude returns a JSON object with all 8 platform outputs in a single call
  4. Content is parsed, stored, and distributed to per-platform editing interfaces

The scheduling system uses a recurrence expansion model. Given a set of recurring schedules $S = {s_1, s_2, \ldots, s_n}$, the calendar generates upcoming posts by expanding each schedule's day-of-week pattern over a $k$-week window:

$$\text{Posts}(S, k) = \bigcup_{i=1}^{n} \bigcup_{d \in \text{dates}(s_i, k)} {(s_i, \text{platform}, d, s_i, \text{time})}$$

Campaign overrides are stored separately and merged at render time, replacing the default brief for any matching date.

Design System

The UI follows a design system built around #E8570A (Claude orange) with:

  • Geist for body text, DM Mono for labels and status badges
  • Flat design — no box shadows, 1px borders, warm neutral palette
  • Sidebar navigation (260px fixed) with a main content panel
  • CSS-only animations: staggered fade-up for cards, skeleton shimmer for loading states, pulsing borders during generation

Voice Profile Auto-Fill

One feature I'm particularly proud of: paste a URL, and Distro fetches the website, strips the HTML to text (capped at 8,000 characters), sends it to Claude, and extracts a complete voice profile — brand name, what they do, tone tags, and "never say" words. The user then edits Claude's suggestions to fine-tune.


Challenges

1. Prompt Engineering for Multi-Platform Output

Getting Claude to output valid JSON with 8 distinct, platform-appropriate pieces of content in a single call required significant iteration. Early prompts would leak LinkedIn's professional tone into the Twitter output, or generate Instagram scripts instead of descriptions. The solution was explicit per-platform instructions embedded in the system prompt with format examples.

2. Resend Initialization at Build Time

The email integration using Resend crashed during next build because new Resend(process.env.RESEND_API_KEY) was evaluated at module level — and the env var wasn't available during static generation. The fix was lazy initialization inside the handler function.

3. Content Persistence Across Navigation

With 8 platform detail pages, navigating away from the dashboard would lose all generated content. I solved this with a localStorage-backed persistence layer (content-storage.ts) that saves generated content, user edits, and post statuses independently, rehydrating on mount.

4. Scheduling Complexity

The calendar scheduler needed to handle: recurring day-of-week patterns, per-schedule default briefs, per-date campaign overrides, and real-time re-rendering when any of these change. The solution was a pure-computation generateUpcomingPosts() function combined with a version counter that triggers useMemo recomputation.


What I Learned

  • Claude's structured output is remarkably reliable when you give it explicit JSON schema instructions and retry on parse failure
  • localStorage is surprisingly effective for demo-grade state management — no database needed for the prototype
  • Design systems matter early. Switching from default Tailwind indigo to a custom warm palette across 26 files was a significant effort that would have been trivial with CSS variables from day one
  • The gap between "functional" and "demo-ready" is mostly about visual confidence — bold typography, tight spacing, and a cohesive color story

Built With

next.js · typescript · tailwind-css · anthropic-api · claude-sonnet · resend

Built With

Share this project:

Updates