PodiumRun the demo
Field guide

Stand it up in an account with your name on it.

Six commands create the services, two put the Worker up, and a one-time screen makes you the owner. Nothing in the sequence talks to us, and no part of it needs our permission. Below is the whole thing in order, plus the four choices that decide whether your speakers can sign in.

Before the first command

Four things get created, and you own them.

A Cloudflare account on the paid Workers plan, a hostname whose DNS Cloudflare already answers for, and Node. That is the entire prerequisite list.

WhatWhat lives in it
A databaseEvery proposal, score, decision, session, speaker and sponsor row you will ever hold. It is in your account, and a CSV export of it needs nobody's permission.
A key-value namespaceThe published schedule snapshot your website reads, and the replay cache that turns a retried write into the same answer instead of a second booking.
A bucketHeadshots, slide decks, signed agreements. An upload carries a short-lived signature and streams straight into the bucket instead of being buffered, so a 200 MB deck is not a timed-out request.
Three queuesOne carries what happened. One carries what goes out: mail, webhooks, spreadsheet pushes. The third keeps whatever failed six attempts, so a bad afternoon is a queue you can read rather than mail nobody can account for.

Create them once. The names are not decorative: the configuration in the repository refers to them, so a bucket called something else is a deploy that fails.

npx wrangler d1 create podium
npx wrangler kv namespace create CACHE
npx wrangler r2 bucket create podium-assets
npx wrangler queues create podium-events
npx wrangler queues create podium-delivery
npx wrangler queues create podium-dlq

The first two print an id each. Keep both; the next section wants them. Two durable objects and a quarter-hourly schedule come with the deploy itself, so there is nothing to create for the reminders, the sweeps or the deadline expiries.

The configuration those names come from →

The deploy

Set three variables, then run two commands.

There are no account ids anywhere in the repository you cloned. Production configuration is generated at deploy time from what you export, which is why your first deploy cannot land in somebody else's database.

  1. Put the three values somewhere your shell can read

    The hostname you will serve on, and the two ids the create commands printed. In a file, not your shell history. The deploy script reads them from the environment and writes a config that is never committed.

    PODIUM_HOSTNAME=cfp.yourconference.com
    CF_D1_DATABASE_ID=…
    CF_KV_CACHE_ID=…
  2. Set the one secret the application cannot start without

    It signs the unsubscribe links, so a speaker can leave a mailing list without signing in. Generating or checking one of those links throws without it.

    npm run deploy:config
    npx wrangler secret put UNSUBSCRIBE_SECRET -c wrangler.production.jsonc
  3. Migrate, then ship

    In that order, always. Migrations are append-only, so the new schema fits the code already running. The reverse order gives you new code reading columns that do not exist yet.

    npm run deploy:migrate
    npm run deploy

The hostname list replaces, it does not merge. Deploying publishes the hostnames you named as the Worker's complete set. A hostname currently served by a different Worker is taken from it, silently. Name every hostname you want served, every time.

The script that generates the config →

First run

Open the setup screen before anyone else does.

Nothing seeds the organization. A freshly migrated deployment has no accounts in it at all, and every path sends you to the same place until one exists.

The screen asks for the conference-running body, a default timezone, a contact address, and your own name, email and password. Submitting it creates the organization, your person record and your ownership of it as one write, then signs you in.

Then it is gone. Every method on that path is refused the instant an organization exists, checked against the database on each request rather than cached. It is a screen that runs with nobody signed in and makes an administrator.

There is a lock under it as well. The first row it claims can hold exactly one value, so two people racing to set up the same empty deployment cannot both win.

Import your speakers after that, not before. One CSV brings people and their history across from the tool you are leaving.

What comes across from the incumbent, and what does not →

The parts that bite

Four choices you make once and live with.

Two of them have already gone wrong on a real deployment of this software, on dates you can go and read. They are here so they do not go wrong on yours.

Sign-ins die on the free plan

A correctly sized password hash costs far more than the ten milliseconds of processor time a free Worker gets per request, so attempts are killed mid-hash and sign-in fails intermittently. This deployment ran that way for months, on a hash deliberately weakened to fit, and moving to the paid plan is what let that be undone. Start on Workers Paid.

Installing an email provider turns password sign-in off

Password sign-in is on wherever no email provider is active, and off the moment one is. That is deliberate: a deployment that can neither send mail nor take a password cannot exist. It also means the afternoon you connect Resend, everyone signing in with a password stops. Tick the box on the settings screen first, or send everybody an invitation link.

The first-pass review is off, and stays off

Nothing switches it on for you. Switched on, its scores sit beside your reviewers' rather than inside them, never count toward quorum, and are always overridable. The evaluator in the box calls no external model at all — it scores structure and says so.

Moving off D1 costs you one file

The database sits behind 388 lines, and no query outside them knows it is talking to D1. Postgres behind Hyperdrive means rewriting that file and nothing above it. The rules do not move, and neither does anything that reads them.

The organization settings screen: name, contact email and primary domain, then an Access section with an 'Allow password sign-in' checkbox, a Review section with an 'AI first-pass review' checkbox, and a draft retention field set to 730 days.
Two of the four are checkboxes on one screen. Both are ticked here because the demo conference opts in to both; a deployment you set up arrives with neither.
Once it answers

Five checks, in the order they fail.

  1. The sign-in page answers

    On a deployment nobody has set up, it redirects to the setup screen, which is the correct answer and not a fault. After setup it renders. This is the same check the build runs against our own deploy.

  2. Create an event and read what it made for you

    A new event arrives with days, rooms, formats, review rubrics, about a dozen speaker onboarding tasks and a draft call. The common mistake is building a second set of tracks beside the ones already there.

  3. Send yourself something and look in the outbox

    With no provider installed, every message is composed, rendered, recorded and never sent. That is a legitimate state to run in, and it is how you rehearse an announcement to four hundred speakers.

  4. Publish a schedule and embed it

    Publishing writes an immutable snapshot. Your marketing site reads that, a CDN caches it, and rolling back is pointing at the previous one. Nothing public touches the live programme tables.

  5. Mint a read-only key and try to read something with it

    A key reaches exactly what its scopes name. Give one only the schedule and confirm for yourself that it cannot see a review or a speaker's email address.

Nobody will do the upgrades. Pulling a release and redeploying is a five-minute job, and it is your job. There is no status page to check and no vendor to email at nine on a Monday. If that is a dealbreaker, it is better to find out today than in April.

What this costs, itemised, and what it costs that is not money →

Connecting a provider

Store the key, then name the key.

Email, chat and spreadsheet sync all install the same way, and the field that trips people up is the same field every time.

The install form asks for a secret reference, not a secret. Put the provider's key in your Worker's secrets under a name you choose, then type that name into the form. A key pasted into that field is a leak, not a configuration, and the application will not find it there anyway.

npx wrangler secret put RESEND_API_KEY -c wrangler.production.jsonc

Then install Resend and put RESEND_API_KEY in the reference field. Health check it from the same screen; a misconfigured provider says what went wrong there rather than in a bounced acceptance three weeks later.

Then go back to the settings screen and decide about passwords, because you have just changed that answer.

Everything that plugs in, and what has no adapter yet →

The bill

One fixed line, and the rest is metered.

The software is free and the licence cannot be revoked. Workers Paid starts at five dollars a month on Cloudflare's own pricing page. That is the only source that binds; it is not our price to promise. Storage, database reads and queue messages are metered and small next to the included allowances, but your event is not our event.

There is no monthly total on this page. Every one on a page like this is a guess you find out was wrong on your own invoice.

Now hand the running instance to an agent →

Run it on your laptop before you run it on your domain.

One command resets a database, seeds a conference already halfway through its year and serves it on port 8787. No Cloudflare account is involved, and everything above is the same software.