AI-Powered Podcast Post-Production Platform
Durable, real-time SaaS workflow pipeline that turns one upload into a full content kit:
upload podcast once โ get a complete content pack (transcript + chapters + highlights + social drafts) with real-time progress updates.
Website: https://podcastogist.com
Instant demo (no login): https://podcastogist.com/demo
Flagship Personal Project: End-to-end SaaS build created to demonstrate full-stack product engineering depth.
Primary Audience: Recruiters, hiring managers, and engineers reviewing architecture.
Build Effort: ~200 hours (Dec 6 โ Dec 30, 2025).
LinkedIn: https://www.linkedin.com/in/lindsaycode/
At a glance:
- ๐ Upload podcast audio once, get recaps, social posts, highlights, hashtags, timestamps, and speaker dialogue.
- โก Durable background orchestration via Inngest with parallel, webhook-driven AI tasks (no long-running HTTP).
- ๐ Clerk-based authentication and plan gating (Free / Plus / Max).
- ๐ก Real-time UI updates via Convex subscriptions (no polling).
- ๐ Full dark mode + mobile-responsive UI.
- ๐งช Deterministic CI test suite (unit + integration + E2E) with real-time Convex wiring.
A fast, 200 seconds, high-signal walkthrough of the product flow, architecture, and quality gates.
This video is meant for people who want the context in moments.
Intro - 0:00 | Product Overview - 0:30 | Architecture Overview - 2:20 | CI & Tests - 3:15
Podcastogist-Video-Walkthrough.mp4
๐ Product Overview
- What Podcastogist Is
- Why It Exists
- Who It's For
- What This Project Demonstrates
- Feature Suite
- Content Pack Output
- Workflow Snapshot
๐ฅ Media & UI
- Video Walkthrough (Watch this to get instant context ๐)
- UI Tour
๐ณ Plans & Gating
๐๏ธ Architecture & Data
- Architecture Overview
- Parallel Orchestration
- Long-Running File Handling
- API Surface
- Event Contracts
- Data Model
- Project Status Model
- Storage Layout
- Security Model
- Observability
๐ง AI & Processing
- AI Generation Engine
- AssemblyAI Transcription
- Prompt & Schema Discipline
- Real-time Updates
- Retry & Regeneration
- Failure Modes & Recovery
- Tradeoffs
๐จ Design & UX
๐งฐ Engineering Notes
- Tech Stack
- Repo Map
- Engineering Notes
- Security & Maintenance
- Tests Suite
- Build Challenges
- Build Timeline
๐ฎ Future & Wrap-up
Podcastogist is an AI-powered podcast post-production platform.
You upload a single audio file of a podcast/interview and receive a complete content pack: Recaps, Social Posts, Titles, Hashtags, YouTube Timestamps, Highlight Moments and the Speaker Dialogue of the whole episode, in minutes.
Itโs built as a full-stack SaaS workflow that emphasizes reliability and real-time UX.
The experience is designed to feel instant even though heavy AI work happens in the background.
Core idea: one upload becomes everything a podcaster needs to publish, promote, and distribute.
Podcast post-production is repetitive, slow, and expensive.
Creators spend hours converting one episode into multiple formats and channel-specific copy.
Podcastogist automates that last-mile distribution layer.
The goal is to free creators from tedious tasks so they can focus on content.
It compresses hours of work into minutes.
Podcastogist is built for creators who ship episodes and then get stuck in the post-production grind.
If you regularly need to:
- Write different copy for multiple social platforms
- Craft titles, descriptions, and hashtags per episode
- Produce chapter markers or clipโworthy moments
โฆthis is the workload Podcastogist automates.
This project is designed to prove full-stack engineering competency end-to-end.
It highlights practical SaaS architecture patterns and real production tradeoffs.
What this build shows:
- โ A production-style, multi-service architecture (auth, storage, DB, workflows, AI providers).
- โ Durable workflow orchestration (retries, step isolation, observability, using Inngest).
- โ Parallelization (fan-out / fan-in) to reduce end-to-end processing time.
- โ Plan-based feature gating with Clerk (Free / Plus / Max) across UI + backend enforcement.
- โ Realtime UI updates driven by Convex database subscriptions (no polling).
- โ Large-file constraints (size + duration) enforced by plan limits with progress, validation, and safe retries.
- โ Long-running transcription handling via webhook-based status updates.
- โ Multi-provider AI pipeline (AssemblyAI + OpenAI) with schema validation.
- โ Clean UI/UX: mobile responsive layout + dark mode toggle.
- โ Type-safe boundaries using schema validation (Zod) and strong TS typing.
- โ Developer experience: consistent formatting/linting (Biome), modern UI primitives.
- โ CI quality gate on every push to main (Biome, typecheck, build).
- โ Deterministic test suite that validates real SaaS workflows (Vitest + Playwright + Mock Providers Ecosystem).
- Long-running AI jobs feel broken without live, reliable progress signals.
- Cost boundaries need real enforcement, not just UI affordances.
- Partial failures must be recoverable without rerunning everything.
Podcastogist is organized around outcomes, not just features.
Each capability exists to ship content faster and smarter.
- Drag-and-drop upload with file type validation and size gating.
- Supports MP3, WAV, M4A, FLAC, OGG, AAC, Opus, WebM, and 3GP/3G2.
- Pre-validation against plan limits before any expensive processing.
- Duration extraction using HTML5 Audio; fallback to file-size estimation.
- Progress bar, status states, and explicit error messaging.
- Speaker diarization enabled for every transcript (access gated by plan).
- Auto chapters generated for better topic segmentation.
- Word-level timestamps + formatted text for higher accuracy context.
- Asynchronous transcription via webhooks (no long-running HTTP).
- Multi-format recaps: full summary, bullet points, insights, and TL;DR.
- Generated with OpenAI Structured Outputs (Zod-validated JSON).
- Uses transcript + chapter context to improve relevance.
- Six platform-specific posts: Twitter/X, LinkedIn, Instagram, TikTok, YouTube, Facebook.
- Each post has its own tone and formatting constraints.
- Copy-to-clipboard UX for each platform card.
Style cues per platform:
- Twitter/X: concise hook with a clear takeaway.
- LinkedIn: professional framing and insight-heavy tone.
- Instagram: caption-style summary with light engagement prompts.
- TikTok: short, energetic copy for clips.
- YouTube: description-friendly summary + context.
- Facebook: community-friendly opener and discussion prompt.
- YouTube short titles, YouTube long titles, podcast titles, SEO keywords.
- Rendered with badges and lists for quick scanning.
- Structured outputs ensure consistent formatting.
- Platform-specific hashtag strategies.
- Visible as inline badges for fast copy/reuse.
- Extracted from AssemblyAI auto chapters.
- Uses accurate timing with human-readable timestamps.
- Presented as a list of โclip-worthyโ sections.
- Hybrid generation: AssemblyAI timestamps + GPT-enhanced chapter titles.
- YouTube-ready formatting (MM:SS or HH:MM:SS + title).
- One-click copy of the entire chapter list.
- Speaker-labeled dialogue with confidence scores.
- Gated to Max plan access (but always captured in transcript).
- Color-coded badges for visual scanning.
- Theme toggle in the navbar (next-themes).
- Works across light/dark with consistent contrast.
- Full mobile responsiveness across key views.
- Locked tabs show upgrade prompts and plan details.
- Upgrade CTAs are contextual to the missing feature.
- Upgrade flow also supports โgenerate missing featuresโ after upgrade.
The output bundle is designed for distribution across major channels.
Outputs generated today:
- Recaps (full, bullets, insights, TL;DR).
- Social posts (Twitter/X, LinkedIn, Instagram, TikTok, YouTube, Facebook).
- Titles (YouTube short/long, podcast titles, SEO keywords).
- Hashtags (YouTube, Instagram, TikTok, LinkedIn, Twitter).
- YouTube timestamps (chapters).
- Highlight moments (clip candidates).
- Speaker dialogue (diarized transcript view).
| Output | Description | Source | Plan Access |
|---|---|---|---|
| Recaps | Full summary + bullets + insights + TL;DR | OpenAI GPT-5-mini | Free / Plus / Max |
| Social Posts | 6 platform-optimized posts | OpenAI GPT-5-mini | Plus / Max |
| Titles | YouTube short/long + podcast + SEO | OpenAI GPT-5-mini | Plus / Max |
| Hashtags | Platform-specific tags | OpenAI GPT-5-mini | Plus / Max |
| Highlight Moments | Clip-worthy moments | AssemblyAI chapters | Max |
| YouTube Timestamps | Chapters with GPT titles | AssemblyAI + OpenAI | Max |
| Speaker Dialogue | Speaker diarization view | AssemblyAI | Max |
Podcastogist follows a durable, step-based processing pipeline.
High-level flow:
- User signs in securely with Clerk (Google or email/password).
- User uploads audio via drag & drop.
- File is uploaded directly to Vercel Blob with progress tracking.
- Server action creates a project and triggers an Inngest workflow.
- AssemblyAI transcribes audio asynchronously via webhook.
- Parallel AI jobs run based on plan gating.
- Results are saved into Convex.
- UI updates in real-time as each step completes.
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#3B82F6','primaryTextColor':'#FFFFFF','primaryBorderColor':'#F97316','lineColor':'#3B82F6','secondaryColor':'#F97316','tertiaryColor':'#FFF7ED'}}}%%
flowchart TD
A[User Uploads Podcast Episode] --> B[File Stored in Vercel Blob]
B --> C[Inngest Event Fired]
C --> D[Project Status: In Progress]
D --> E[AssemblyAI Transcription]
E --> F[Parallel AI Output Generation]
F --> G[Outputs Saved to Convex]
G --> H[Project Status: Completed]
H --> I[User Views Project Detail page]
I --> J[Live Updates via Convex]
Each collage shows the same page in both themes (light/dark) and viewports (desktop/mobile).
Desktop + Mobile โข Light + Dark

Drag & drop upload UX + plan limits messaging.
Desktop + Mobile โข Light + Dark

Project detail view while AI jobs are running.
Desktop + Mobile โข Light + Dark

Completed project with all unlocked content tabs.
Desktop + Mobile โข Light + Dark

Locked tab with upgrade prompt and plan benefits.
Desktop + Mobile โข Light + Dark

Multiple projects running with live status updates.
Desktop + Mobile โข Light + Dark

Multiple finished projects in the dashboard.
Desktop + Mobile โข Light + Dark

First-time user experience with empty projects list.
Desktop โข Light + Dark

Plan comparison and upgrade CTAs.
Desktop โข Light + Dark

Pricing and limits are enforced consistently in UI, server actions, and API routes.
| Plan | Price | Summary |
|---|---|---|
| Free | $0 | Recaps only โ try the workflow with strict limits |
| Plus | $21/month | Social posts + titles + hashtags |
| Max | $34/month | Full intelligence pack: timestamps, highlights, speaker dialogue |
Note: Not a real commercial SaaS. This is a demonstration project.
Podcastogist is a fully working platform built for podcast post-production jobs & content kit generation.
Billing runs in test mode (Clerk/Stripe), so no real charges are processed.
| Plan | Max Projects | Max File Size | Max Duration |
|---|---|---|---|
| Free | 3 (lifetime, incl. deleted) | 10MB | 10 minutes |
| Plus | 30 (active only) | 200MB | 2 hours |
| Max | Unlimited | 3GB | Unlimited |
Edge cases (intended behavior):
- Free plan counts lifetime projects (including deleted).
- Plus plan counts active projects (deleting frees slots).
- Max plan removes quota limits but still enforces file type validation.
- Client-side pre-validation in the upload flow.
- Server-side validation in
validateUploadAction. - API route enforcement in
/api/upload. - Convex project counting (Free plan counts deleted projects, Plus plan counts active).
This is enforced by Clerk Billing features and app-side checks.
| Feature | Free | Plus | Max |
|---|---|---|---|
| Recaps | โ | โ | โ |
| Social Posts | โ | โ | โ |
| Titles | โ | โ | โ |
| Hashtags | โ | โ | โ |
| YouTube Timestamps | โ | โ | โ |
| Highlight Moments | โ | โ | โ |
| Speaker Dialogue | โ | โ | โ |
- AI costs scale with file length and number of outputs.
- Free proves value; Plus unlocks distribution outputs.
- Max unlocks deep intelligence (timestamps, highlights, speaker dialogue).
Feature access is enforced at multiple layers to prevent bypass.
Plan detection (Clerk):
has({ plan: "free" | "plus" | "max" })is used to determine access.- Feature availability is mapped in
PLAN_FEATURES.
UI gating:
- Locked tabs show an Upgrade Prompt with plan details.
- Feature tabs are wrapped with
Protectto guard access.
Workflow gating:
- Inngest pipeline conditionally schedules AI jobs by plan.
- Recaps always run; advanced tasks only run for Plus/Max.
End-to-end pipeline from upload to real-time results.
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#3B82F6','primaryTextColor':'#FFFFFF','primaryBorderColor':'#F97316','lineColor':'#3B82F6','secondaryColor':'#F97316','tertiaryColor':'#FFF7ED'}}}%%
flowchart LR
A[Client Upload] --> B[Vercel Blob]
A --> C[Server Actions]
C --> D[Convex: create project]
C --> E[Inngest event: podcast/uploaded]
E --> F[AssemblyAI transcription]
E --> G[OpenAI generation]
F --> H[Convex: save transcript]
G --> H[Convex: save outputs]
H --> I[Realtime UI updates]
I --> J[Dashboard tabs]
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#3B82F6','primaryTextColor':'#FFFFFF','primaryBorderColor':'#F97316','lineColor':'#F97316','secondaryColor':'#F97316','tertiaryColor':'#FFF7ED'}}}%%
flowchart LR
UI[Client UI] --> NX[Next.js]
NX -->|URLs only| CX[(Convex)]
NX -->|File bytes| BL[(Vercel Blob)]
IN[Inngest] -->|Reads transcript| AI[AI Providers]
AI -->|Structured outputs| IN
IN -->|Writes results| CX
CX --> UI
AI generation runs in parallel to reduce total time.
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#3B82F6','primaryTextColor':'#FFFFFF','primaryBorderColor':'#F97316','lineColor':'#3B82F6','secondaryColor':'#F97316','tertiaryColor':'#FFF7ED'}}}%%
flowchart TD
A[AssemblyAI Transcription Step Complete] --> B{Fan-out}
B --> C[Recaps]
B --> D[Social Posts]
B --> E[Titles]
B --> F[Hashtags]
B --> G[YouTube Timestamps]
B --> H[Highlight Moments]
C --> I[Join]
D --> I
E --> I
F --> I
G --> I
H --> I
I --> J[Save to Convex]
J --> K[UI updates]
Execution detail:
- Uses
Promise.allSettledto allow partial success. - Errors are tracked per job and saved to
jobErrors. - Retry flow regenerates a single failed output without rerunning the entire pipeline.
Long transcriptions are handled asynchronously to avoid server timeouts.
Key mechanism:
- AssemblyAI transcription is submitted with a webhook URL.
- Inngest
step.waitForEventwaits forassemblyai/transcript.status. - The webhook endpoint (
/api/webhooks/assemblyai) forwards status events to Inngest.
Why this matters:
- No long HTTP requests in Next.js server functions.
- Durable wait with retries if webhooks fail.
- The UI remains responsive and accurate throughout processing.
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#3B82F6','primaryTextColor':'#FFFFFF','primaryBorderColor':'#F97316','lineColor':'#3B82F6','secondaryColor':'#F97316','tertiaryColor':'#FFF7ED','signalColor':'#F97316','noteBkgColor':'#3b83f687','noteTextColor':'#FFFFFF'}}}%%
sequenceDiagram
participant UI as UI
participant NX as Next.js
participant IN as Inngest
participant AS as AssemblyAI
participant CX as Convex
UI->>NX:
note over UI,NX: upload audio
NX->>CX:
note over NX,CX: create project(status=uploaded)
NX->>IN:
note over NX,IN: emit podcast.uploaded event
IN->>CX:
note over IN,CX: update status=processing
IN->>AS:
note over IN,AS: start transcription
AS-->>NX:
note over AS,NX: webhook status updates(transcriptId, status)
NX->>CX:
note over NX,CX: update jobStatus/transcript fields
IN->>CX:
note over IN,CX: write AI outputs as ready
CX-->>UI:
note over CX,UI: realtime updates (subscription)
These endpoints enable the full workflow while keeping UI clean.
POST /api/uploadโ generates Vercel Blob upload tokens.POST /api/inngestโ Inngest webhook endpoint for execution.POST /api/webhooks/assemblyaiโ AssemblyAI webhook receiver.
validateUploadActionโ plan limit validation.createProjectActionโ create project + trigger Inngest event.deleteProjectActionโ delete project + blob cleanup.generateMissingFeaturesโ generate newly unlocked features after upgrade.
Inngest events are strongly typed and used to orchestrate work.
{
"name": "podcast/uploaded",
"data": {
"projectId": "<convex_id>",
"userId": "<clerk_user_id>",
"plan": "free | plus | max",
"fileUrl": "https://<blob_url>",
"fileName": "episode-42.mp3",
"fileSize": 12345678,
"mimeType": "audio/mpeg"
}
}{
"name": "podcast/retry-job",
"data": {
"projectId": "<convex_id>",
"job": "socialPosts | titles | hashtags | highlightMoments | youtubeTimestamps | recaps",
"userId": "<clerk_user_id>",
"originalPlan": "free | plus | max",
"currentPlan": "free | plus | max"
}
}{
"name": "assemblyai/transcript.status",
"data": {
"projectId": "<convex_id>",
"transcriptId": "<assemblyai_id>",
"status": "completed | error",
"error": "<error_message>"
}
}Convex uses a single denormalized projects table to store everything.
This allows real-time updates and atomic writes as jobs complete.
{
_id: "projects:abc123",
userId: "user_123",
deletedAt: undefined,
inputUrl: "https://<blob_url>",
fileName: "episode-42.mp3",
displayName: "My Episode 42",
fileSize: 12345678,
fileDuration: 3672,
fileFormat: "mp3",
mimeType: "audio/mpeg",
status: "completed",
jobStatus: {
transcription: "completed",
contentGeneration: "completed"
},
transcript: {
text: "...",
segments: [
{
id: 0,
start: 0,
end: 12,
text: "Welcome to the show...",
words: [
{ word: "Welcome", start: 0, end: 1 },
{ word: "to", start: 1, end: 2 }
]
}
],
speakers: [
{
speaker: "A",
start: 0,
end: 12,
text: "Welcome to the show...",
confidence: 0.94
}
],
chapters: [
{
start: 0,
end: 60000,
headline: "Opening",
summary: "Introductions and episode goals",
gist: "intro"
}
]
},
highlightMoments: [
{
time: "00:42",
timestamp: 42,
text: "Key takeaway",
description: "A strong quote worth clipping"
}
],
recaps: {
full: "Full summary...",
bullets: ["Point one", "Point two"],
insights: ["Insight one"],
tldr: "One sentence hook"
},
socialPosts: {
twitter: "Tweet...",
linkedin: "LinkedIn post...",
instagram: "IG caption...",
tiktok: "TikTok caption...",
youtube: "YouTube description...",
facebook: "Facebook post..."
},
titles: {
youtubeShort: ["Short title"],
youtubeLong: ["Long title"],
podcastTitles: ["Podcast title"],
seoKeywords: ["keyword1", "keyword2"]
},
hashtags: {
youtube: ["#podcast"],
instagram: ["#podcast"],
tiktok: ["#podcast"],
linkedin: ["#podcast"],
twitter: ["#podcast"]
},
youtubeTimestamps: [
{ timestamp: "0:00", description: "Intro" },
{ timestamp: "4:12", description: "Main topic" }
],
error: { message, step, timestamp },
jobErrors: { recaps, socialPosts, titles, hashtags, youtubeTimestamps, highlightMoments },
createdAt: 1733420000000,
updatedAt: 1733420500000,
completedAt: 1733420600000
}Indexes: by_user, by_status, by_user_and_status, by_created_at.
Status is used to drive UI state and processing feedback.
uploadedโ project created, waiting to process.processingโ Inngest workflow running.completedโ all scheduled jobs finished.failedโ workflow or a critical step failed.
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#3B82F6','primaryTextColor':'#FFFFFF','primaryBorderColor':'#F97316','lineColor':'#3B82F6','secondaryColor':'#F97316','tertiaryColor':'#FFF7ED'}}}%%
stateDiagram-v2
[*] --> uploaded
uploaded --> processing
processing --> completed
processing --> failed
failed --> [*]
completed --> [*]
transcription: pending โ running โ completed/failed.contentGeneration: pending โ running โ completed/failed.
- Individual AI jobs record errors inside
jobErrors. - The UI surfaces per-tab retries for only the failed output.
Storage is split between Blob (files) and Convex (structured results).
Vercel Blob:
- Input audio files stored via direct client upload.
- Public URLs with randomized suffixes.
Convex:
- Stores metadata + AI outputs.
- Stores transcript structure (segments, chapters, speakers).
Example key structure (conceptual):
blob://uploads/{userId}/{random}.mp3blob://uploads/{userId}/{random}.wav
Podcastogist is designed for practical, application-level security.
Access control:
- Clerk enforces authentication at the app boundary.
- Convex queries/mutations are user-scoped.
Blob access model:
- Vercel Blob client uploads require
access: "public". - URLs are unlisted and stored privately in Convex.
- Projects are only accessible to the owning user.
Practical outcome:
- Public blob URLs exist, but access is effectively private by design.
- This is appropriate for podcast media workflows.
High-signal visibility across every stage of the pipeline.
- Inngest: step runs, retries, and execution history.
- Convex: mutation/query logs and realtime subscriptions.
- Vercel: request/runtime logs + Blob activity.
- Providers: AssemblyAI + OpenAI dashboards for usage and errors.
OpenAI is used for recaps, posts, titles, and hashtags.
Key characteristics:
- Model:
gpt-5-mini(fast, cost-effective). - Structured outputs enforced via Zod schemas.
- Inngest
step.ai.wrap()ensures observability and retries.
Why structured outputs matter:
- Enforces predictable JSON shape.
- Prevents UI breakage from malformed model output.
- Keeps type safety consistent across the stack.
AssemblyAI is used for high-quality transcription and audio intelligence.
Enabled features:
- Speaker diarization (always captured).
- Auto chapters (topic detection with summaries).
- Word-level timestamps.
- Formatted text (punctuation + capitalization).
Design rationale:
- Strong diarization performance.
- Async transcription API fits durable workflows.
- Chapters provide reliable timing for highlights and YouTube timestamps.
Prompting is structured and reproducible across all AI steps.
Prompts include:
- Partial transcript text for context.
- AssemblyAI chapters for structure.
- Platform-specific rules and constraints.
Schemas enforce:
- Output shape for recaps, titles, posts, hashtags.
- Minimum/maximum array lengths.
- Character limits (Twitter/X).
Convex makes the UI reactive without manual polling.
What updates in real-time:
- Project list as uploads complete.
- Project detail tabs as AI outputs arrive.
- Processing status and progress states.
Why this matters:
- UI feels โliveโ even for long processing steps.
- Users always see accurate status from the database.
Podcastogist supports selective regeneration.
Retry flow:
- Failed tabs can be retried individually.
- Inngest function reuses stored transcript data.
- Job errors clear automatically after success.
Upgrade flow:
- Upgrading plans unlocks new features.
- A single action generates all missing outputs.
Performance choices:
- Parallel AI fan-out (Promise.allSettled).
- Async transcription via webhooks.
- No polling thanks to Convex subscriptions.
- Client-side direct Blob uploads for large files.
This system is built to behave predictably when things go wrong.
| Area | Failure | User impact | Recovery behavior |
|---|---|---|---|
| Upload | file too large | blocked before processing | clear error + upgrade CTA |
| Upload | unsupported type | blocked | supported formats listed |
| Upload | blob upload fails | project not created | retry upload |
| Transcription | AssemblyAI error | no outputs | project marked failed |
| Webhook | late/duplicate webhook | delayed updates | idempotent handling |
| Generation | OpenAI error | missing outputs | per-job retry |
| Gating | locked feature access | upgrade prompt | no compute runs |
| Limits | duration exceeds plan | blocked early | limit message shown |
| Realtime | subscription drop | stale UI | auto reconnect/refresh |
| Deletion | project removed | disappears from list | Free counts include deleted |
Deliberate choices to balance reliability, speed, and scope.
Chosen intentionally:
- Managed services for faster iteration and fewer ops burdens.
- Durable workflows instead of long-running API requests.
- Realtime subscriptions instead of client polling.
- Progressive enrichment instead of โall-or-nothingโ output.
- Direct-to-Blob uploads for large files.
Not chosen (for this scope):
- Self-hosted workers and queues.
- One mega prompt for all outputs.
- Storing raw audio in the database.
- Hard-blocking UI until all outputs finish.
- Private blob access with signed URLs (future enhancement).
The UI is built to feel premium and confident.
Visual system:
- Gradient sunrise theme (blue โ orange).
- Glassmorphism cards with soft elevation.
- Clear hierarchy with bold typography.
Interaction design:
- Copy-to-clipboard actions for outputs.
- Progress indicators tied to real-time data.
- Upgrade prompts placed at the moment of need.
Micro-interactions and layout decisions that improve usability.
Upload UX:
- Immediate file validation feedback.
- Duration display before upload starts.
- Clear upload status states.
Dashboard UX:
- Tabs for outputs to prevent scrolling fatigue.
- Distinct states for processing vs completed.
- Empty state guidance for first-time users.
- Processing shown as a pipeline, not just a spinner.
Accessibility: ARIA-labeled theme toggle, focusable actions, and contrast-safe colors.
A modern, production-grade stack optimized for SaaS workflows.
Frontend:
- Next.js 16 (App Router).
- React 19 + TypeScript.
- Tailwind CSS v4 + shadcn/ui + Radix UI.
- Lucide icons + react-social-icons.
- Sonner for toast feedback.
Backend & Infrastructure:
- Clerk (auth + billing + feature gating).
- Convex (realtime DB + reactive queries).
- Inngest (durable background jobs).
- AssemblyAI (transcription + diarization).
- OpenAI GPT-5-mini (content generation).
- Vercel Blob (large file storage + direct uploads).
Quality & DX:
- Zod for schema validation.
- Biome for linting/formatting.
- CI quality gate on every push to main (Biome, typecheck, build).
- Vitest for unit + integration tests; Playwright for E2E.
- Typed AI outputs via Structured Outputs.
- Dependabot for automated dependency updates.
- CodeQL for security scanning.
Key directories and their roles.
app/โ Next.js App Router pages + API routes.components/โ UI components, tabs, upload flow, layout.actions/โ Server actions for uploads, projects, retries.convex/โ Schema + queries + mutations.inngest/โ Workflow functions and AI steps.lib/โ Shared utilities, types, constants, clients.schemas/โ Zod schemas for AI outputs.tests/โ Test suites and fixtures..github/โ GitHub workflows and repo assets.
These are the decisions that made the product reliable.
- Direct Blob upload via
@vercel/blob/client. - Server action pre-checks prevent wasted uploads.
- API route enforces plan-based size limits.
- Inngest functions run steps with retries and observability.
- Transcription waits on webhook events (not HTTP timeouts).
- Parallel steps run independently to avoid cascading failures.
- Convex schema supports progressive updates.
jobStatusenables granular UI progress.jobErrorssupports per-feature retry.
- Clerk billing features map directly to app capabilities.
- Free tier project count includes deleted projects.
- Plus tier counts only active projects (fair usage).
- Automated dependency updates (Dependabot)
- Static analysis / code scanning (CodeQL)
This repo is maintained with automated update and scanning workflows to keep dependencies current and reduce risk.
Dependabot PRs are grouped to avoid noise while still surfacing meaningful updates on a weekly cadence.
CodeQL runs on pushes, PRs, and a scheduled job to catch common security issues early.
Together, these checks keep the Security tab active and provide a visible maintenance signal in GitHub PRs.
This suite is designed to prove real SaaS workflows, not toy tests.
It validates the same contracts used in production while keeping CI deterministic and fast.
| Layer | Framework | What it validates | Examples |
|---|---|---|---|
| Unit | Vitest | Gating logic and limits | Plan limits, feature gating, plan-to-feature mapping |
| Integration | Vitest | Contract-level correctness | Zod output schemas, Inngest event shapes, webhook mapping, golden AI outputs |
| E2E | Playwright | Real user workflows | Auth gate, create project, upload โ processing โ results, plan CTA, mobile nav, retry |
Folder layout: tests/unit, tests/integration, tests/e2e
To make end-to-end testing real yet repeatable, external providers are mocked at the boundary:
MOCK_AI=1returns structured AI fixture outputs that match production Zod schemas.MOCK_TRANSCRIPTION=1returns transcript + chapter fixtures (no AssemblyAI calls).MOCK_PIPELINE=1runs the pipeline synchronously with mocked providers.MOCK_AUTH=1+MOCK_PLAN=free|plus|maxsimulate auth + plan gating.
Convex is not mocked: E2E runs against a real local Convex instance, so mutations, subscriptions, and status transitions are exercised exactly as in production.
All three suites run in GitHub Actions on every push to main with named checks:
- โ CI Tests (Unit)
- โ CI Tests (Integration)
- โ CI Tests (E2E)
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#3B82F6','primaryTextColor':'#FFFFFF','primaryBorderColor':'#F97316','lineColor':'#3B82F6','secondaryColor':'#F97316','tertiaryColor':'#FFF7ED'}}}%%
flowchart LR
A[Vitest + Playwright] --> B[Mock Flags]
B --> C[Mock Providers<br/>AI + Transcription]
A --> D[Next.js App]
D --> E[Convex Local DB]
E --> F[Realtime UI Updates]
C --> D
Why this matters: It proves the full pipeline (upload โ processing โ results) without paid APIs, and keeps CI both reliable and high-signal.
What required real engineering problem-solving.
1) Long-running transcription on serverless
- Solved using AssemblyAI webhooks + Inngest durable waits.
2) Large file uploads
- Solved with direct-to-Blob uploads and server-side size constraints.
3) Plan gating without security holes
- Solved with multi-layer enforcement in UI, server actions, and routes.
4) Partial failure tolerance
- Solved with
Promise.allSettledand per-job error tracking.
5) Real-time UX
- Solved with Convex subscriptions and atomic mutations.
The project was built in an intense sprint schedule.
Timeline (2025):
- Dec 6 โ Dec 25: Architecture + core build.
- Dec 25 โ Dec 30: Packaging, video walkthrough, README prep.
- Total effort: ~200 hours.
Future enhancements that would extend the product.
Product expansions:
- Batch processing for multiple episodes.
- Multi-language transcription and translation (currently only English is supported)
- Custom brand voice profiles for AI outputs.
- Export packs (PDF / Notion / Google Docs).
Distribution enhancements:
- Social scheduling integrations (Buffer, Hootsuite).
- Analytics dashboard for content performance.
- A/B testing for titles and hooks.
Workflow upgrades:
- Captions/SRT export.
- Team workspaces and roles.
- Webhook notifications when processing completes.
A quick reference for reviewers and recruiters.
- Inngest โ Durable job orchestration layer.
- Convex โ Real-time database with reactive queries.
- Clerk โ Authentication + billing + plan gating.
- AssemblyAI โ Speech-to-text + audio intelligence.
- OpenAI GPT-5-mini โ LLM used for content generation.
- Vercel Blob โ Object storage for uploaded audio files.
- Plan gating โ Feature access based on subscription tier.
- Diarization โ Identifying who spoke in a transcript.
- Auto chapters โ AssemblyAI topic segmentation.
- Structured outputs โ LLM responses constrained by Zod schemas.
Not a real commercial SaaS. This is a demonstration project.
Podcastogist is a fully working platform built for podcast post-production jobs & content kit generation.
Billing runs in test mode (Clerk/Stripe), so no real charges are processed.
- GitHub Issues: https://github.com/lindsaycode05/podcastogist/issues
- Email: [email protected]
- LinkedIn: https://www.linkedin.com/in/lindsaycode/
