PhotoDrop.
Event photos in guests' hands the same day. Scan a QR code, see your photos, add your own, download them all.
After a wedding or a launch, guests wait days for photos. Then they arrive one at a time over WhatsApp, compressed and out of order.
Photographers were juggling Google Drive links, USB sticks and email chains. Guests who took their own photos had no way to add them to the event, and nobody could find the photos from the first dance without scrolling through thousands.
One QR code per event. Everything else happens behind it.
A photographer creates an event and uploads the whole shoot at once. Guests scan the QR code at the venue and see the gallery in seconds, with no account and no app to install.
- Bulk uploadThousands of photos in one go, straight to storage with live progress.
- QR accessEvery event gets a printable QR code. Guests scan and they're in.
- Guest uploadsGuests add their own photos and short clips until a closing date the owner sets.
- MomentsBrowse by time, so the first dance is easy to find.
- Download allOne zip of the whole gallery, built in the background.
- PrivacyLocation and device data are removed from guest photos before anyone sees them.

The API never carries a photo.
Uploads go straight from the browser to S3 using signed, multi-part uploads. The API only records what arrived. Resizing, video conversion and zips run on separate workers, so a busy event never slows the gallery down.
Choices that made it hold up.
Guest uploads jump the queue
Guest photos are processed ahead of the photographer's bulk import, so a guest's picture shows up in seconds even while 10,000 others are waiting.Why: guests leave if they don't see their photo quickly
Workers size themselves to the machine
Each worker reads the CPU and memory it starts on and picks how many photos and videos to process at once.Result: 12-second clips went from 6 to 9.7 per minute
Zips stream, never sit in memory
"Download all" streams photos from storage into a new upload, about 16 MB of memory for a 2 GB zip.Why: a 500-photo zip can't be allowed to crash a worker
Counters update once per batch
Photo and storage counts update once per database statement, not once per row, with a fixed lock order so busy events never deadlock.Result: a 20,000-row import went from about a minute to a few seconds
Nothing gets stuck
Every five minutes the worker re-queues anything stuck in "uploaded". Deleted photos stay recoverable for 30 days, then are purged.Why: a Redis blip should never lose someone's photo
Two kinds of access
Photographers sign in with rotating tokens. Guests get a scoped access token from the QR code that only opens that one event.Why: no guest accounts, and no guest can see another event
What each stage looked like on this project.
- 01IdeaOne problem: guests waiting days for event photos. First release scoped to scan, view, download.
- 02DesignDirect-to-storage uploads, a job queue and separate workers designed before any code.
- 03BuildTypeScript API and workers, raw SQL with 14 numbered migrations, React front end.
- 04TestEnd-to-end security checks on guest uploads, privacy, blocking and zips.
- 05DeployDocumented deployment, Docker stacks for development and testing, S3 bucket policies written down.
- 06SecureScoped guest tokens, rate limits, metadata stripped from guest photos, guest blocking.
- 07ScaleLoad, crowd and resilience tests against a throwaway stack.
- 08MaintainAutomatic recovery of stuck jobs, 30-day trash, zip expiry, counter recalculation.
Every scenario runs against a disposable copy of the system.
| Check | What it does | Result |
|---|---|---|
| owner10k | One photographer uploads 10,000 photos | Gallery and downloads hold at 10k |
| read100k | Gallery reads with 110,000 photos in one event | Cursor paging stays fast |
| guest1000 | One guest uploads 1,000 × 12 MP photos, then a 500-photo zip | Processed and zipped |
| crowd | 200 guests uploading and 1,000 viewing at once | Guest uploads stay first in line |
| resilience | Kill the worker, kill the API, restart Redis, freeze Postgres | Recovers without losing photos |
| bulk | Bulk inserts with concurrent writers | No deadlocks · minutes to seconds |
| capacity | Photo and clip throughput at different worker settings | Video +62% |
$ npm run loadtest:up # start the throwaway stack $ npm run loadtest:owner10k # 10,000 photos, gallery and downloads $ npm run loadtest:crowd # 200 uploading + 1,000 viewing $ npm run loadtest:resilience # break things on purpose $ npm run loadtest:down # remove the stack and its data
Find every photo you're in. Face matching is designed and planned for the next phase: guests take one selfie and get back every photo they appear in.
Have an idea or a system to build?
Tell me what you need. I'll reply within a day with questions, a rough approach, or a time to talk.