03 / Product · Platform · Theatre tech

Impresario

The operating system for the small stage: a multi-tenant box office and production platform for 50 to 300 seat theatres.

Impresario brand card: a lit theatre marquee at dusk reading Plan the show, Run the show, Grow the show

Summary

A community or off-Broadway theatre with 200 seats runs on the same infrastructure as a Broadway house, minus the budget and the staff. Impresario is one product that covers the whole loop: sell the ticket, run the show, keep the patron. It started as the ticketing system for Threshold Theatre Collective's first production, built against a fixed opening night, and grew from operating it live into a multi-tenant platform where every theatre gets its own branded box office, its own Stripe account, its own sending domain, and the backstage tools no ticketing vendor ships.

Context

Ticketing platforms take a cut of everything, including comps, cash at the door, and the free preview for the board. Per-ticket fees are opaque: patrons see a service charge the theatre cannot explain and often eats part of. Everything backstage lives somewhere else, cast contact sheets in a doc, conflicts in a spreadsheet, rehearsal calls in a group text, patron emails in a mailing list nobody has cleaned in years. And the box office volunteer on opening night needs a check-in tool that works when the lobby Wi-Fi does not. Threshold hit all of this at once when it needed to sell tickets for Jekyll & Hyde, and the options for a small theatre were enterprise-priced or unusable. So I built it.

What I built

  • A public box office per theatre: branded ticketing site on their own domain or a free subdomain, show pages with calendar, cast and creative bios, content warnings, ADA information, and structured data for search. Checkout with general-admission tiers, discount codes, comps, waitlists, guest purchase, refunds, and exchanges
  • A mobile-first admin console for people doing this between a day job and a tech rehearsal: analytics, shows and performances, orders, check-in, attendance, refunds, patrons, and settings
  • Patron CRM and lifecycle marketing: ticket buyers, waitlist signups, and company members roll up into one patron record with lifetime value and tags. Visual segments (superfans, lapsed 90 days, comp-only, first-timers) feed campaigns and automated emails: post-show thank-yous, win-backs with a generated one-use code, new-season announcements
  • Backstage, the part no ticketing vendor ships: company roster, cast conflicts, a rehearsal and call-sheet builder that cascades block times, and a subscribable company calendar
  • Show night: one-tap check-in with an offline queue to IndexedDB that reconciles when connectivity returns, PIN-gated so a volunteer can run the door from their own phone without an admin account
  • Per-tenant integrations: each theatre connects its own Stripe account, verifies its own sending domain through generated SPF, DKIM, and DMARC records with per-record polling and a one-click handoff to their web person, and optionally attaches its own mailing-list audience with encrypted credentials
  • Email branded by audience: messages to patrons and company members wear the theatre's brand, account and platform messages wear Impresario's, and every link resolves to the tenant's own domain

Screens

Two Impresario admin screens: a box office dashboard with tickets sold, gross sales, house sold, and comps issued, and a settlement view with online sales, cash at the door, comps, and net to the theatre
Box office. What sold while you slept, and a settlement that balances online, cash, and comps per performance with card fees in the open.
The money model: the theatre keeps face valueA patron pays the ticket price plus one all-in fee. Stripe on the theatre's own account processes the card at cost. The theatre keeps face value exactly. The platform keeps the remainder. The same fee formula runs in the client quote, the checkout session, webhook fulfilment, refunds and exchanges, and the settlement report. Comps and cash sales carry no platform fee.THE MONEY MODELThe theatre keeps face value, exactlyOne all-in fee shown to the patron. No markup on comps or cash sales.01Patron paysTicket price$28.00One all-in fee+ $1.80Total$29.8002StripeThe theatre's own accountCard processing at costFunds never touch the platformPayout to the theatre's bank03Theatre keeps$28.00Face value, exactly04Platform keepstotalminus card processingminus face valueThe margin, and nothing elseSAME FORMULA EVERYWHEREOne shared module, imported by the front end and the edge functions, is authoritative in five places:client quoteserver checkoutwebhook fulfilmentrefunds and exchangessettlement reportComps and cash sales carry no platform fee at all
The money model. One all-in patron fee, the theatre keeps face value exactly, and comps and cash carry no fee at all.
Backstage overview in the admin console showing a production binder with rehearsals, show calls, conflicts, and invites
Backstage. The production binder: schedule health, conflicts, and call sheets at a glance.
Patron CRM in the admin console with total patrons, combined lifetime value, tickets sold, comps issued, and a searchable patron list
Patrons. One record per person with lifetime value and history, feeding segments, campaigns, and automations.
Impresario multi-tenant architectureThree kinds of host, the marketing site, the product host, and each theatre's ticketing domain, are one React application. A host router and tenant resolver classifies the request to an organization. Downstream sit Postgres with row-level security, edge functions, and per-tenant integrations, all sharing one money module.MULTI-TENANT ARCHITECTUREOne deployment, many theatresThe host decides who you are. The database enforces it.MARKETING SITEgetimpresario.comPricing, FAQ, beta intakePRODUCT HOSTimpresario.showSignup and the admin consoleTENANT HOSTtickets.theatre.orgThe theatre's branded box officeHost router and tenant resolverclassifyHost() resolves the request to an organizationby domain, subdomain, or explicit overrideDATAPostgres and RLSEvery table scoped to an organizationSecurity-definer role checksA bug in a query cannot leak another theatreSERVICESEdge functionsCheckout, refunds, exchangesEmail, automations, cronTenant resolved on every requestPER-TENANT INTEGRATIONSTheir own accountsStripe Connect, their accountSending domain, their DNSMailing list, their audienceShared money moduleone fee formula: client quote, checkout, fulfilment, refunds, settlement
Architecture. One deployment, three kinds of host. The host decides who you are, and the database enforces it.

Screenshots use a demonstration theatre with invented productions, patrons, and dollar figures. No real customer data appears.

How it works

  • 01

    Host

    The marketing site, the product host, and every theatre's ticketing domain are one React application. A host classifier resolves the request to an organization.

  • 02

    Tenant

    Branding, navigation, email sender, and Stripe account all follow from that resolution. Row-level security scopes every table to the organization.

  • 03

    Checkout

    One fee formula in one shared module, imported by the front end and the edge functions, so the quote, the charge, and the settlement never drift.

  • 04

    Show night

    PIN-gated check-in with an offline queue, so a dead lobby signal does not stop the house from opening.

  • 05

    Patron

    Buyers, waitlists, and company members roll up into one record. Segments feed campaigns and lifecycle emails in the theatre's own brand.

Detail

The theatre keeps face value, exactly

This was the hardest product decision and the clearest differentiator. The patron sees a single line item covering card processing and the platform margin. The theatre receives the printed ticket price, and comps and manually recorded cash sales carry no fee at all. The engineering consequence is that a single fee formula has to be authoritative in five places: the client-side quote, the server checkout session, webhook fulfilment, refunds and exchanges, and the settlement report. It lives in one shared module imported by both the front end and the edge functions. An earlier version had the client estimate and the server compute independently; they drifted by cents, and cents are exactly what a treasurer notices.

Outcome

It sold the tickets and ran the door for Threshold's first production, and Threshold now runs on it as the first tenant on its own domain. Impresario is in private beta with theatres onboarding onto their own Stripe accounts and sending domains. Shipped and in use: ticketing, checkout, refunds and exchanges, waitlists, reminders, check-in, patron CRM, segments, campaigns, lifecycle automations, backstage scheduling, and per-show branding. On the roadmap: reserved-seating charts, bulk patron import, season subscription packages, and accounting exports.

0

Fee formula, authoritative in five places

0

Platform fee on comps and cash sales

0

Kinds of host served by one deployment

What I would highlight

Tenant resolution as a first-class concern: two production domains serving different things from one deployment forced host classification into its own tested module rather than scattered location checks. Row-level security recursion, solved with a security-definer helper that returns the caller's accessible show IDs, a pattern reused across the schema. Idempotency everywhere money moves, because Stripe and cron both retry. And self-serve onboarding for non-technical admins, because the artistic director should be able to set up a sending domain without calling anyone.

My role

Founding team of two: I own product and engineering, my co-founder owns brand and design. I designed and built the platform, and I operated it live for a production, which is where the product scope came from.

Stack

  • React
  • Vite
  • TypeScript
  • Tailwind
  • Postgres
  • Row-level security
  • Edge functions
  • Stripe Connect
  • Resend
  • Mailchimp
  • IndexedDB