Skip to content

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

DomainServicePurpose
app.podcasterplus.comCloudflare PagesMain dashboard application
feed.podcasterplus.comCloudflare WorkersRSS feed generation
media.podcasterplus.comCloudflare R2Media file CDN
book.podcasterplus.comCloudflare PagesGuest booking portal (active)

Technology Stack

Frontend

TechnologyVersionPurpose
SvelteKit2.xApplication framework
Svelte5.xUI components (Runes)
TypeScript5.xType safety
Tailwind CSS3.xStyling
shadcn-svelteLatestUI component library

Backend

TechnologyPurpose
HonoAPI framework (mounted at /api/*)
SupabaseDatabase, auth, real-time
PostgreSQLPrimary data store
Cloudflare WorkersEdge compute

Hono API Layer

All backend APIs are implemented using Hono and mounted at /api/* via hooks.server.ts.

Route GroupPathAuthPurpose
Health/api/healthNoService health checks
Bookings/api/bookingsMixedBooking CRUD and lifecycle
Booking Links/api/booking-linksYesEvent type management
Availability/api/availabilityNoTime slot availability
Calendars/api/calendarsYesCalendar connections
Google OAuth/api/auth/googleNoOAuth callback
Prep Questions/api/prep-questionsYesInterview prep config
Automations/api/automationsYesAutomation engine
Media/api/mediaMixedFile upload and audio
RSS/api/rssYesFeed cache invalidation
Stripe/api/stripeYesCheckout and portal
Webhooks/api/webhooksSig*External service webhooks

*Webhooks use signature verification instead of Bearer tokens.

See the Hono API Reference for complete documentation.

Infrastructure

ServiceProviderPurpose
PagesCloudflareApplication hosting
WorkersCloudflareEdge functions (15 production workers)
R2CloudflareObject storage (media bucket)
KVCloudflareKey-value caches (RSS, public API)
QueuesCloudflareAsync messaging (9 queues + dead letters)
HyperdriveCloudflareDatabase connection pooling
Image TransformsCloudflareOn-demand image resizing

Cloudflare Workers

The full per-worker reference lives in Cloudflare Workers. Production workers:

WorkerPurposeTrigger
RSS FeedGenerate podcast RSS feedsHTTP + Queue
Media DeliveryServe media R2 bucket; log downloads to WAEHTTP
Public API & EmbedPublic JSON API v1 + embeddable playerHTTP + Queue
Analytics RollupWAE → daily download aggregates + analytics triggersCron (hourly)
Scheduled PublisherAuto-publish episodes at scheduled timeCron (every minute)
Automation SchedulerProcess time/event-based automation triggersCron + Queue
Automation ExecutorExecute actions (email, webhook, field update)Queue
Notification SchedulerClaim due notification deliveriesCron (every minute)
Notification ExecutorDeliver notifications (email/push/in-app)Queue
Podcast Import ExecutorMirror back-catalogue audio/assets; resolve external linksQueue
Lifecycle ManagerPause expiry, hard-delete, R2 cleanup, watchdogsCron
AI ProcessorAI jobs (transcription, generation, research) via WorkflowsQueue (ai-jobs)
Distribution MonitorCheck directory listing status; emit automation eventsCron (hourly)
CRM SyncNightly CRM sync + daily admin digestCron
DR ReplicatorReal-time R2 → Backblaze B2 replicationQueue (R2 events)

Cloudflare Queues

QueueProducerConsumerPurpose
rss-invalidationMain App, Scheduled Publisher, Import ExecutorRSS Feed WorkerCache invalidation
podcast-importsMain App (Imports + External Link APIs), Lifecycle ManagerPodcast Import ExecutorPer-episode back-catalogue or hosting-migration job
external-episode-linkMain App (publish-handoff + External Link API), Lifecycle Manager, Podcast Import Executor (self-retry)Podcast Import ExecutorResolve a pending external episode to its RSS <guid> (Epic 11)
automation-eventsMain App, Analytics Rollup, Distribution MonitorAutomation SchedulerEvent-based triggers
automation-executionsAutomation SchedulerAutomation ExecutorAction execution
notification-deliveriesNotification SchedulerNotification ExecutorNotification delivery intents
ai-jobsMain App (AI API routes)AI ProcessorSpawn AI job Workflow instances
public-api-invalidationMain App (fans out alongside rss-invalidation)Public API WorkerDrop KV payload cache for embed player / v1 API
r2-object-eventsR2 event notificationsDR ReplicatorReplicate media objects to Backblaze B2
dead lettersautomation-dlq, podcast-imports-dlq (5 attempts), external-episode-link-dlq, notification-deliveries-dlq, ai-jobs-dlq, r2-object-events-dlqManualFailed 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 Update

Media Upload

Browser

   ├─1─▶ Request presigned URL (Hono API)

   ├─2─▶ Direct upload to R2 (presigned)

   └─3─▶ Confirm upload (Hono API) → Database update

RSS 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 → Return

Guest 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:

TablePurposeUse Case
podcast_membersAuthorizationWho can access the podcast NOW
episode_creditsAttributionWho appears in episode history
episode_guestsGuest AccessPortal access via magic tokens

Participation Layer:

TablePurposeUse Case
episode_peopleRosterWho 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 TypeStorageUsed By
API KeysCloudflare SecretsWorkers, Pages
Database CredentialsHyperdrive ConfigRSS Worker
Webhook SecretsCloudflare SecretsMain App

Performance Targets

MetricTargetNotes
Page Load (P50)< 1sDashboard pages
API Response (P50)< 200msHono endpoints
RSS Feed (cached)< 100msKV cache hit
RSS Feed (uncached)< 500msDatabase query
Media Delivery< 50msR2 edge cache

Monitoring & Observability

Logging

  • Pages: console.log → Cloudflare Logs
  • Workers: console.logwrangler 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:

LayerTechnologyUse CasePerformance
ClientFuse.jsCommand palette, filters0ms (in-memory)
ServerPostgreSQL FTSGlobal 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:

ComponentTechnologyPurpose
Rich Text EditorTipTapUser-facing editing experience
CRDT SyncYjsConflict-free document merging
TransportSupabase RealtimeBroadcast channels for sync
PersistenceIndexedDBOffline editing support

Collaboration Data Flow:

User Edit → TipTap → Yjs Doc → Supabase Broadcast → Other Clients

                    IndexedDB (offline)

                    PostgreSQL (save)

See Collaboration System for implementation details.

Internal documentation - Not for public distribution