
Showrium turns one piece of material into a post for every platform, written in the user’s voice. You can read about the product here. This post covers how it’s built and how we got it ready to accept code from strangers on the internet without putting our users or their data at risk.
Three principles shaped almost every decision:
Cost first. If it’s cheap to run, it can be cheap to use everywhere.
No single point of dependence. No single AI provider, and no platform we can only reach by scraping.
Safety enforced by machines, not by memory. A rule only counts if something checks it automatically.
The architecture: one Worker, deliberately boring
Showrium runs as a single Cloudflare Worker. It serves both the React 19 app and the API (built with Hono). Data lives in D1 (SQLite at the edge), and files live in R2.
apps/web the Worker: React app + Hono API
apps/admin staff-only admin app, its own Worker, behind Cloudflare Access
packages/core business logic: workspaces, content, images, publishing, billing, credits
packages/db Drizzle schema and SQL migrations
packages/llm prompts, the AI provider chain, web research
packages/platforms one adapter per social platform
packages/policy per-platform rules and a linter for every post
packages/sdk-ts a TypeScript SDK generated from our OpenAPI specWhy one Worker? There are no servers to manage and no idle cost. It starts quickly everywhere, and the app and API can never drift apart. The web app calls the same public API that developers and the MCP server use.
Cost first: do expensive work where it’s free
The cheapest computer is the one the user already has open.
Image crops and designed cards are made in the browser, on a canvas, from the original at full resolution, then uploaded. The server never resizes anything. It checks each file’s real type from its bytes and stores it.
Short explainer videos are rendered in the browser too. The timeline is drawn on a canvas and recorded with the voice-over, so there’s no render farm and no GPU bill.
iPhone photos (HEIC) are converted in the browser. The converter only loads when it’s needed.
Every AI call is recorded: by day, by feature, by model and by workspace. That gives us a real cost per post, and it tells us which workspaces cost more than they pay. We show users their own share on the Billing page. Being open about what things cost is part of the product.
No single AI provider
Writing goes through a provider chain. If one provider fails, rate-limits us or changes its prices, the next one takes over:
DeepSeek V4 Flash → Gemini Flash-Lite (both through OpenRouter) → Cloudflare Workers AI.
The prompts are built so the model can’t add facts. A post may only contain what’s in the user’s material, what the user asked for, or a source the research step found and listed. A policy linter then checks each post against its platform’s rules: length, where links go, disclosures, and repeated sentences.
Platforms: official routes only
We don’t scrape. Showrium reads and posts only through official APIs and feeds.
That rule has real product consequences, and we designed for them rather than around them:
LinkedIn hides a post’s image when the post contains a link. So the source link goes in the first comment, which Showrium adds for you.
X charges a lot more for posts with links, and its rules restrict automated quotes and mentions. So an X post with a link, a quote or a mention is always “Post it myself”: ready to paste, free, one click. X posts without links can still be automated, after a one-time agreement that X’s rules require.
Automation behaves like a person, not a cron job. Posts go out at different minutes, at most one per time slot and at least 4 hours apart. There are daily limits per platform. News is never scheduled more than 48 hours after it came out.
Data can’t be lost by a merge
This is the part we’re proudest of, because it’s what makes going public safe.
Migrations only ever add. Schema changes are generated with Drizzle and applied by CI before each deploy. Because we never drop or rename anything the running app uses, the old code and the new schema always work together. A deploy can’t break the app halfway through. “Only add” is a promise, and promises get broken at 2 a.m. So we wrote a migration guard that runs as a required check on every pull request:
Merged migrations are frozen. It compares the PR against its base: no migration may be edited, renamed, or removed, and the migration journal may only grow.
Destructive statements are refused: DROP, DELETE FROM, TRUNCATE, RENAME, REPLACE INTO, and switching off foreign keys (which is how SQLite rebuilds a table).
Data backfills (UPDATE … SET) need a marker on their own line, which code owners must review.
The guard has its own tests, and CI runs them before the guard itself. A safety check that nobody tests isn’t a safety check.
No direct path to production
Showrium has two environments, and there’s only one way into each:
your branch or fork ──PR──▶ develop ──checks + code owner──▶ preview (automatic)
│
└──release PR──▶ main ──checks + code owner──▶ production
(waits for a named approver)Every change passes four required checks:
s/n | Check | What it does |
|---|---|---|
1 | | lint, typecheck, about 400 unit tests and a production build |
2 | | real browser tests of the core flows, against a fake AI with random secrets, so they cost nothing and touch no real account |
3 | | gitleaks over the full git history |
4 | | the data protection described above |
5 |
On top of the checks:
Branch rules: develop and main accept changes only through pull requests. Each needs a code owner’s approval, which is cleared again if new commits are pushed, plus resolved review threads. Force pushes and branch deletion are blocked. Even org admins can only bypass through a pull request, never by pushing directly.
Deploy keys exist in exactly one place: GitHub environments that release them only to their own branch. Preview’s key only reaches runs from "develop"; production’s key only reaches main. Production also waits for a human to approve every deploy.
A pull request from a fork never sees a secret. That’s not a policy, it’s simply how the pipeline is wired.
After every deploy, a smoke test checks the live health endpoint. We tag a release only after production is confirmed healthy.
Supply chain and access
Actions and tools are pinned: every third-party GitHub Action to a full commit SHA, the runner image to ubuntu-24.04, and the gitleaks image to an exact version. Dependabot proposes updates, and they go through the same pipeline into develop.
GitHub’s own scanning is on: CodeQL covers TypeScript and our workflow files, plus secret scanning with push protection and private vulnerability reporting. We cleared every open finding before opening the doors.
People: two-factor authentication is required for everyone in the organisation. Access is given through teams, never to individuals one at a time. People without a current role were removed. The GITHUB_TOKEN is read-only by default, and workflows can’t approve pull requests.
Old credentials: we searched for anything still using our old deploy token, then revoked it.
The admin app: powerful, but it can’t destroy anything
Staff tools live in a separate worker, behind Cloudflare Access. Every request checks Access’s signed token and the person’s staff role.
It has no destructive operations at all. The strongest thing it can do is a reversible pause: a workspace keeps everything, and its posting simply waits. A pause needs an owner and a written reason, and it’s logged.
Open source, properly
Showrium is licensed under AGPL-3.0, with a commercial license from Nextrium for cases where the AGPL doesn’t fit. The TypeScript SDK is Apache-2.0, so you can use it anywhere.
Contributors sign a CLA once, and a check confirms it on every pull request. Tasks live in the Nextrium contributors portal. GitHub issues are kept for findings: bugs, gaps, and risks. Everything a newcomer needs is in docs/HANDBOOK.md: how the code is organized, how work moves from a task to a release, the standards, and what “done” means.
What we’d tell another team
Make the safe path the only path. We didn’t write “please don’t push to main”. We made it impossible.
Test your safety checks. The migration guard’s own tests caught a real gap: it was comparing against the last commit instead of the files on disk, so uncommitted local edits slipped past.
Free tiers go a long way if you design for them. Showrium runs on Cloudflare’s free and low-cost tiers. Rendering in the browser and choosing AI models by price do most of the work.
Be honest with users about cost. It changed our product for the better: “Post it myself” exists because we were honest that X links are expensive for us.
Come build with us
The code is at github.com/Nextrium/showrium. Pick a task from the contributors portal, read the handbook, and open a pull request into develop. The checks will tell you quickly whether you’re on track, and a code owner will review it.