Case study: product build, live in production

A whole wedding, running on one link.

A designed invitation, guest RSVP, and secure photo delivery share one multi-tenant platform that a venue can run every wedding on. I built it in days and hardened it like it took months.

Type

Multi-tenant web app

Stack

Next.js · Supabase · ImageKit

Languages

English and Thai, per wedding

Status

Live, with a full public demo

The brief:
replace the paper trail.

A wedding runs on logistics,
and the logistics still run on chat apps and spreadsheets.

Ask anyone who has organised a wedding what the hard part was, and they rarely say the ceremony. It’s the guest list. Invitations go out over chat, RSVPs come back as a thumbs-up emoji or not at all, and someone keeps a spreadsheet that’s out of date the day it’s made. Three months after the event, the photos are still sitting in a photographer’s transfer link that expired last week.

The brief was to put all of that on one link. A guest receives an invitation that looks designed, with the couple’s names, their colours, their story, the venue, and the schedule. They answer the RSVP on the same page. The couple watch responses arrive in a dashboard instead of a group chat. After the wedding, the same link delivers the real photos, privately, to the people who were there.

One more requirement made it a platform rather than a website: it had to be multi-tenant. The economics of a one-off wedding site are terrible, because every couple needs one for a single day. But a venue hosts weddings every week. Build it so each wedding is its own tenant with its own guests, media, and settings, and the same build serves every event the venue ever hosts. That’s what I built: the machine that produces wedding websites.

The public demo, Lily & Tan at The Garden House, Bangkok, is tenant number one. It runs on the production system in English and Thai, with a real guest list, real RSVPs, and a real gallery.

What was built

Three surfaces in one system,
for the guest, the couple, and the owner.

The invitation

A page that reads like stationery

Each wedding gets a designed page with a monogram, palette, story, schedule, and venue. It’s rendered from the couple’s own content and colours instead of a theme with the names swapped. The demo wedding runs a sage-and-cream identity with a bilingual toggle.

RSVP

Answers that land in a dashboard

Guests respond on the invitation itself with their attendance, party size, and notes. Responses are tracked per invite, so the couple can see who has answered, who is bringing whom, and who needs a nudge, without keeping a spreadsheet alive by hand.

Photo delivery

The gallery, after the day

When the photographer delivers, the same link becomes the album: a private gallery with downloads for guests. Media is served through signed URLs, so images and videos are only reachable through the platform, and a hotlinked address returns nothing.

Phases

One link, two lives

A wedding page has a before and an after. The platform models that as a phase switch: invitation-and-RSVP mode until the day, and gallery-and-thanks mode after. It flips by hand or on a date, so the link printed on the physical card never changes.

Sign-in

No passwords at a wedding

Guests and couples sign in with a one-time code sent to their email, so there’s nothing to create and nothing to forget between the save-the-date and the album. The auth layer issues the codes and sends them from a verified sending domain.

The dashboard

Where the owner runs it all

Each wedding has its own controls for invites, guests, RSVPs, media, phase, and publishing. Because the system is multi-tenant, the venue’s operator manages every event from the same place, with each wedding sealed off from the others.

The hard part was
the walls between the pages.

Tenancy is a security problem

Multi-tenant means every wedding’s guests, RSVPs, and photos live in the same database. So the walls between weddings have to be real: the database itself refuses to return another wedding’s data.

Enforced at the database

Access rules are row-level security policies in Postgres instead of checks in the application. The API layer exposes the database directly, so an app-layer guard is decoration. The policy is the wall, and it holds for every client ever written.

Attacked before launch

The build was audited adversarially, with probes for role escalation, cross-tenant writes, and shared-row leaks. Those are the classic multi-tenant failure modes. Three real isolation holes were found this way and fixed in the policy layer instead of patched in the UI.

Media locked the same way

Photos and videos are served only through signed URLs, with enforcement switched on at the CDN, so an unsigned request is refused. A wedding album is private by construction, not by obscurity.

From the live demo

Captured from the production system.
Nothing here is a design mock-up.

The demo wedding’s story and schedule sections, in the couple’s own palette
Lily & Tan: the invitation reads like stationery.
The wedding invitation on a phone, where most guests open it
Most guests open the link on a phone, so the page is built for that first.

Questions worth asking

Yes. The template page embeds the live demo wedding, Lily & Tan at The Garden House, Bangkok. It runs on the production platform with a real guest list, RSVPs, and gallery, in English and Thai.

It was built venue-first: a wedding venue or planner runs every event they host on it, with each wedding sealed off as its own tenant. It works just as well for a single couple who want their invitation, RSVP, and album on one link, and the demo wedding is exactly that.

That’s the point of the case study. The shape is a branded public page, a private dashboard, secure media, and multiple tenants. It fits venues, studios, agencies, and any business that runs events or clients through a link. Pricing has the real numbers, and every project gets a fixed quote before work starts.

Because “the app hides it” is not the same as “the system refuses it.” A guest list and a private album are personal data. If the walls between customers are only in the interface, one bug exposes someone’s wedding. Enforcing isolation in the database means every path to the data hits the same wall, including paths nobody has built yet.

The build itself took days. Modern tooling is that fast in the right hands. What the schedule didn’t skip was the hardening: an adversarial security pass over tenancy, roles, and media access, with the holes it found fixed at the database layer before anything shipped.

Have a business that runs on events,
clients, or bookings?

This platform started as one sentence in a brief. Send yours, and you’ll get a straight answer and a fixed quote.