All work

The Armour Wedding

RSVPs, menu choices, song requests and shared photos — a platform for our own wedding

  • No application server — the browser is the backend
  • Scan a table card, land in the photo uploader
  • Real RSVPs from real guests, in two languages

We got married in May 2026, and the whole run-up went through a site I built for it: the invitation, who was coming, what each of them wanted to eat, the songs they wanted played, and — on the day itself — the photos they took. Guests come in English and Polish, so it shipped in both. The site is retired now that the day has passed; the screenshots here come from a local copy seeded with fictional guests.

The constraint I set was that one person should be able to run it with almost no server to maintain. A single small Lambda handles the password gate; everything else is the browser talking straight to managed services.

The day at a glance: a live countdown, the timeline, and the menu everyone would later choose from.

No server, just the browser and managed pieces

There is no application backend. The Vue SPA reads and writes Supabase — Postgres with an auto-generated REST layer — directly from the client for everything relational: parties, guests, menu, songs. Photos go to AWS S3, with the browser getting temporary upload credentials from a Cognito Identity Pool rather than a presign endpoint I'd have to run. Song search hits the Spotify Web API. The one gate — the password on the private pages — is an AWS Lambda behind a Function URL. The whole thing is served as a static build from AWS Amplify Hosting.

That shape is the point: it's cheap, it deploys as static files, and there's no server process to keep alive for a site that only had to survive a few months of real traffic.

The RSVP

Every guest belongs to a party with a short code. Enter the code — or arrive on a link that carries it — and the app loads that party and its people, and asks each of them, individually, for a starter, a main and a dessert. Children get their own path: a half-sized adult portion or a set kids' menu.

One party, every guest: each person picks their own three courses, and the children get a kids' menu.

Song requests are part of the same flow. A search box queries Spotify live from the browser, and the chosen track travels with the RSVP so it lands in the same place as everything else.

Song requests search Spotify directly from the browser; the chosen track rides along with the RSVP.

Behind it, a password-gated dashboard reads every party and guest and aggregates it entirely on the client into the numbers a caterer actually needs: who's confirmed, and how many of each dish to cook.

The admin view aggregates every response in the browser — who's coming, and how many of each dish to make.

The physical-to-digital bridge is my favourite part. Each printed code encodes a URL rather than an id. A guest's card carries a link that deep-links straight to their own prefilled RSVP — the same ?code= the form reads, so scanning it skips the code entry entirely.

The photos card goes one better. Its code encodes …/photos?password=…&lang=…, and the router treats a ?password= in the URL as an instruction to unlock. So a guest scans the card on the table, and lands directly in the unlocked, correct-language photo uploader — no code, no password prompt, nothing to type. The codes themselves are drawn with qr-code-styling, rounded and centred on our initials.

A printed code that's really a magic link — scan it and land in the unlocked, correct-language uploader.

Uploading the day

The photos page lets guests upload straight from their phones. Because the browser holds Cognito credentials, each file goes directly to S3 with no upload server in the middle. There's an in-browser camera too, capturing video with a MediaRecorder codec fallback chain (VP9, then WebM, then MP4) so it works across the phones people actually brought.

Guests uploaded straight to S3 from their phones; no app server sat in the middle.

Everything lands in a shared gallery with a lightbox. Downloading uses the Web Share API where a phone supports it, with a fallback that staggers the downloads one second apart — because Android Chrome quietly blocks a burst of simultaneous ones.

Two languages

It was a UK–Polish wedding, so there are two full editions — English and Polish — sharing one Supabase project. The Polish site is the same RSVP over the same guest data, reading its own menu_items_pl table so the courses come through translated.

The Polish edition: the same RSVP, a separate build, reading its own menu table.

What I'd change

Two things, both from hindsight rather than regret.

The Polish edition should have been proper localisation inside one codebase, not a second repository. Forking it was simply faster under a fixed date, and the cost showed up later as two builds to keep in step by hand — exactly what an i18n layer and a single deploy would have removed.

And the RSVP submit writes one guest at a time in a loop. It's fine at a wedding's scale, but it's a batched upsert wearing the wrong clothes: one round trip would be correct where several do the job today.