Architecture Overview
show.fm is a podcast hosting SaaS platform built on the "BESS" stack: Browser/SvelteKit, Edge/Cloudflare, Supabase, Svelte 5.
System Architecture
┌────────────────────────────────────────────────────────────────────────────┐
│ CLIENT LAYER │
├────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Web Browser │ │ Podcast Apps │ │ RSS Readers │ │
│ │ (Dashboard) │ │ (Apple, Spotify)│ │ (Overcast, etc) │ │
│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │ │
└─────────────┼──────────────────────┼──────────────────────┼────────────────┘
│ │ │
▼ ▼ ▼
┌────────────────────────────────────────────────────────────────────────────┐
│ CLOUDFLARE EDGE │
├────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ DNS (podcasterplus.com) │ │
│ └───────┬───────────────────┬───────────────────┬────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌───────────────┐ ┌─────────────────────────────────────────────┐ │
│ │ Pages │ │ Cloudflare Workers │ │
│ │ (SvelteKit) │ │ │ │
│ │ │ │ ┌─────────┐ ┌───────────┐ ┌─────────────┐ │ │
│ │ app.podcaster │ │ │RSS Feed │ │Scheduled │ │ Automation │ │ │
│ │ plus.com │ │ │Worker │ │Publisher │ │ Scheduler │ │ │
│ │ │ │ │(feed.) │ │(cron) │ │ (cron+queue)│ │ │
│ │ book.podcaster│ │ └────┬────┘ └─────┬─────┘ └──────┬──────┘ │ │
│ │ plus.com │ │ │ │ │ │ │
│ └───────┬───────┘ │ ┌────┴────────────┴──────────────┴───────┐ │ │
│ │ │ │ Automation Executor │ │ │
│ │ │ │ (queue consumer: actions) │ │ │
│ │ │ └────────────────────────────────────────┘ │ │
│ │ └─────────────────────┬───────────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Supporting Services │ │
│ │ │ │
│ │ ┌──────┐ ┌──────┐ ┌──────────────────────────────────┐ ┌─────┐ │ │
│ │ │ R2 │ │ KV │ │ Queues │ │Hyper│ │ │
│ │ │Media │ │Cache │ │ rss-invalidation | auto-events │ │drive│ │ │
│ │ │ │ │ │ │ auto-executions | auto-dlq │ │ │ │ │
│ │ └──────┘ └──────┘ └──────────────────────────────────┘ └──┬──┘ │ │
│ │ │ │ │
│ └────────────────────────────────────────────────────────────┼────┘ │
│ │ │
└─────────────────────────────────────────────────────────────────┼──────────┘
│
▼
┌────────────────────────────────────────────────────────────────────────────┐
│ SUPABASE LAYER │
├────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ PostgreSQL │ │ Auth (GoTrue) │ │ Storage │ │
│ │ (Database) │ │ (Users/JWT) │ │ (Backups) │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────────────┘Domain Architecture
| Domain | Service | Purpose |
|---|---|---|
app.podcasterplus.com | Cloudflare Pages | Main dashboard application |
feed.podcasterplus.com | Cloudflare Workers | RSS feed generation |
media.podcasterplus.com | Cloudflare R2 | Media file CDN |
book.podcasterplus.com | Cloudflare Pages | Guest booking portal (active) |
Technology Stack
Frontend
| Technology | Version | Purpose |
|---|---|---|
| SvelteKit | 2.x | Application framework |
| Svelte | 5.x | UI components (Runes) |
| TypeScript | 5.x | Type safety |
| Tailwind CSS | 3.x | Styling |
| shadcn-svelte | Latest | UI component library |
Backend
| Technology | Purpose |
|---|---|
| Hono | API framework (mounted at /api/*) |
| Supabase | Database, auth, real-time |
| PostgreSQL | Primary data store |
| Cloudflare Workers | Edge compute |
Hono API Layer
All backend APIs are implemented using Hono and mounted at /api/* via hooks.server.ts.
| Route Group | Path | Auth | Purpose |
|---|---|---|---|
| Health | /api/health | No | Service health checks |
| Bookings | /api/bookings | Mixed | Booking CRUD and lifecycle |
| Booking Links | /api/booking-links | Yes | Event type management |
| Availability | /api/availability | No | Time slot availability |
| Calendars | /api/calendars | Yes | Calendar connections |
| Google OAuth | /api/auth/google | No | OAuth callback |
| Prep Questions | /api/prep-questions | Yes | Interview prep config |
| Automations | /api/automations | Yes | Automation engine |
| Media | /api/media | Mixed | File upload and audio |
| RSS | /api/rss | Yes | Feed cache invalidation |
| Stripe | /api/stripe | Yes | Checkout and portal |
| Webhooks | /api/webhooks | Sig* | External service webhooks |
*Webhooks use signature verification instead of Bearer tokens.
See the Hono API Reference for complete documentation.
Infrastructure
| Service | Provider | Purpose |
|---|---|---|
| Pages | Cloudflare | Application hosting |
| Workers | Cloudflare | Edge functions (15 production workers) |
| R2 | Cloudflare | Object storage (media bucket) |
| KV | Cloudflare | Key-value caches (RSS, public API) |
| Queues | Cloudflare | Async messaging (9 queues + dead letters) |
| Hyperdrive | Cloudflare | Database connection pooling |
| Image Transforms | Cloudflare | On-demand image resizing |
Cloudflare Workers
The full per-worker reference lives in Cloudflare Workers. Production workers:
| Worker | Purpose | Trigger |
|---|---|---|
| RSS Feed | Generate podcast RSS feeds | HTTP + Queue |
| Media Delivery | Serve media R2 bucket; log downloads to WAE | HTTP |
| Public API & Embed | Public JSON API v1 + embeddable player | HTTP + Queue |
| Analytics Rollup | WAE → daily download aggregates + analytics triggers | Cron (hourly) |
| Scheduled Publisher | Auto-publish episodes at scheduled time | Cron (every minute) |
| Automation Scheduler | Process time/event-based automation triggers | Cron + Queue |
| Automation Executor | Execute actions (email, webhook, field update) | Queue |
| Notification Scheduler | Claim due notification deliveries | Cron (every minute) |
| Notification Executor | Deliver notifications (email/push/in-app) | Queue |
| Podcast Import Executor | Mirror back-catalogue audio/assets; resolve external links | Queue |
| Lifecycle Manager | Pause expiry, hard-delete, R2 cleanup, watchdogs | Cron |
| AI Processor | AI jobs (transcription, generation, research) via Workflows | Queue (ai-jobs) |
| Distribution Monitor | Check directory listing status; emit automation events | Cron (hourly) |
| CRM Sync | Nightly CRM sync + daily admin digest | Cron |
| DR Replicator | Real-time R2 → Backblaze B2 replication | Queue (R2 events) |
Cloudflare Queues
| Queue | Producer | Consumer | Purpose |
|---|---|---|---|
rss-invalidation | Main App, Scheduled Publisher, Import Executor | RSS Feed Worker | Cache invalidation |
podcast-imports | Main App (Imports + External Link APIs), Lifecycle Manager | Podcast Import Executor | Per-episode back-catalogue or hosting-migration job |
external-episode-link | Main App (publish-handoff + External Link API), Lifecycle Manager, Podcast Import Executor (self-retry) | Podcast Import Executor | Resolve a pending external episode to its RSS <guid> (Epic 11) |
automation-events | Main App, Analytics Rollup, Distribution Monitor | Automation Scheduler | Event-based triggers |
automation-executions | Automation Scheduler | Automation Executor | Action execution |
notification-deliveries | Notification Scheduler | Notification Executor | Notification delivery intents |
ai-jobs | Main App (AI API routes) | AI Processor | Spawn AI job Workflow instances |
public-api-invalidation | Main App (fans out alongside rss-invalidation) | Public API Worker | Drop KV payload cache for embed player / v1 API |
r2-object-events | R2 event notifications | DR Replicator | Replicate media objects to Backblaze B2 |
| dead letters | automation-dlq, podcast-imports-dlq (5 attempts), external-episode-link-dlq, notification-deliveries-dlq, ai-jobs-dlq, r2-object-events-dlq | Manual | Failed messages |
Data Flow
User Authentication
Browser → Supabase Auth → JWT Token → API Requests
│
▼
Supabase RLS (Row Level Security)Podcast Management
User Action (Dashboard)
│
▼
SvelteKit Form/Action → Hono API → Supabase PostgreSQL
│ │
│ ▼
│ Queue Message → RSS Worker → KV Invalidation
│
▼
UI UpdateMedia Upload
Browser
│
├─1─▶ Request presigned URL (Hono API)
│
├─2─▶ Direct upload to R2 (presigned)
│
└─3─▶ Confirm upload (Hono API) → Database updateRSS Feed Request
Podcast App/Reader
│
▼
rss.cdn.media/{podcast_id} (slug-form URLs 301 here)
│
├─── Cache HIT ───▶ Return from KV (fast)
│
└─── Cache MISS ──▶ Hyperdrive → PostgreSQL
│
▼
Generate XML → Store in KV → ReturnGuest Booking Flow
Guest
│
├─1─▶ View booking page (book.podcasterplus.com/{host}/{link})
│
├─2─▶ GET /api/availability?date=...&timezone=...
│ │
│ ├─── Fetch host calendar connections
│ ├─── Refresh Google access tokens
│ ├─── Query Google Calendar freebusy API
│ └─── Return available time slots
│
├─3─▶ POST /api/bookings (create booking)
│ │
│ └─── Save booking (status: pending)
│
└─4─▶ Host confirms → POST /api/bookings/:id/confirm
│
├─── Create Google Calendar event
├─── Auto-generate Google Meet link
├─── Send guest email with calendar invite
└─── Update booking (status: confirmed)See Google Calendar Integration for details.
Multi-Tenancy Model
show.fm uses a multi-table permission model: three security-boundary tables plus a participation layer.
Security Boundaries:
| Table | Purpose | Use Case |
|---|---|---|
podcast_members | Authorization | Who can access the podcast NOW |
episode_credits | Attribution | Who appears in episode history |
episode_guests | Guest Access | Portal access via magic tokens |
Participation Layer:
| Table | Purpose | Use Case |
|---|---|---|
episode_people | Roster | Who is active on an episode (links to credits via credit_id) |
See Multi-Tenancy Documentation for details.
Security Architecture
Authentication
- Supabase Auth: JWT-based authentication
- RLS Policies: Row-level security on all tables
- API Auth: Bearer token validation via Hono middleware
Authorization
- Role-Based: Owner, Admin, Editor, Viewer roles per podcast
- Function-Based:
get_podcast_role()PostgreSQL function - Policy-Based: RLS policies enforce access at database level
Secrets Management
| Secret Type | Storage | Used By |
|---|---|---|
| API Keys | Cloudflare Secrets | Workers, Pages |
| Database Credentials | Hyperdrive Config | RSS Worker |
| Webhook Secrets | Cloudflare Secrets | Main App |
Performance Targets
| Metric | Target | Notes |
|---|---|---|
| Page Load (P50) | < 1s | Dashboard pages |
| API Response (P50) | < 200ms | Hono endpoints |
| RSS Feed (cached) | < 100ms | KV cache hit |
| RSS Feed (uncached) | < 500ms | Database query |
| Media Delivery | < 50ms | R2 edge cache |
Monitoring & Observability
Logging
- Pages:
console.log→ Cloudflare Logs - Workers:
console.log→wrangler tail - Supabase: Built-in logging dashboard
Metrics
- Cloudflare Analytics (requests, bandwidth, errors)
- Supabase Dashboard (queries, connections, storage)
- Stripe Dashboard (payments, subscriptions)
Search System
show.fm implements a two-layer search strategy:
| Layer | Technology | Use Case | Performance |
|---|---|---|---|
| Client | Fuse.js | Command palette, filters | 0ms (in-memory) |
| Server | PostgreSQL FTS | Global search, cross-table | <50ms (GIN indexed) |
FTS-Enabled Tables: episodes, automation_rules, notification_templates
See Search System for implementation details.
Collaboration System
show.fm includes real-time collaborative editing for show notes:
| Component | Technology | Purpose |
|---|---|---|
| Rich Text Editor | TipTap | User-facing editing experience |
| CRDT Sync | Yjs | Conflict-free document merging |
| Transport | Supabase Realtime | Broadcast channels for sync |
| Persistence | IndexedDB | Offline editing support |
Collaboration Data Flow:
User Edit → TipTap → Yjs Doc → Supabase Broadcast → Other Clients
↓
IndexedDB (offline)
↓
PostgreSQL (save)See Collaboration System for implementation details.