OwnStory
A live product that turns photos, videos, and voice notes into a private story page people unlock with one QR scan — for gifts, weddings, memorials, birthdays. Real users, real traffic, no app required. I led the 3-month pivot that shipped it as three production surfaces.
May 3, 2026
Problem
Constraints
What I built
Key decision
Outcome
Hindsight
Architecture notes
Two pieces shape how this product surface holds together.
Per-tenant custom domains, atomic onboarding
Each surface — ownstory.com, eternstory.com, eternaltribute.com — resolves to the same Next.js application. Middleware reads the request host, resolves it to a tenant record, and attaches tenant configuration to the request context so downstream code (UI, API, jobs) can read tenant-aware data and branding without per-domain code paths.
Onboarding is atomic in the literal sense: a tenant going live happens inside a single transaction that creates the tenant record, provisions storage, registers the domain, and emits a QR code that links the physical product (memorial plaques, tribute pages) to the digital surface. Either every step succeeds or none of them do. There is no half-onboarded state for support to clean up later.
The tenant configuration layer
Each tenant has a config record describing branding, feature flags, content rules, and surface-specific copy. UI components, API handlers, and job workers all read the config from request context — the same React component renders ownstory's "create a story" affordance and eternstory's "create a memorial" affordance from the same code path, because the surface-specific copy and feature gates live in tenant config, not in component code.
That layer is what makes shipping a new product variant a config change. Without it, every variant becomes a fork of the codebase, and forks compound badly.
Why the multi-tenant bet held
As the founding engineer, three apps would have been three deploys, three on-call surfaces, three sets of dependencies to keep current. Engineering surface area would have grown with product surface area, and at solo capacity that's the failure mode that kills a pivot before it ships.
One codebase plus per-tenant configuration meant the surface area scales with tenants instead of apps — and tenant onboarding is a config record, not a build. That property is the difference between shipping a pivot in three months and not shipping it at all.