Littelist
A family gift-giving coordinator where Grandma doesn't need an account — one source of truth for who's buying what, and no more duplicate Lego sets at Christmas.
- StatusLive at littelist.com
- ScopeSolo, end-to-end product
- TimelineYears brewing · launched Dec 2025
- UseFree · gifters need no account
Full product walkthrough coming soon.
Birthdays and Christmas turn into a scramble across a dozen group chats. Did anyone get the Lego set? I already bought that. What size does he wear now?
The result is duplicate gifts, wasted money, stuff the kid didn't want, and a surprising amount of landfill. One parent ends up as the bottleneck — fielding questions at 10pm, reconciling purchases in their head, and quietly matching a wishlist to a dozen well-meaning relatives. The app that fixes this has to work for both sides: a parent who's happy to log in, and a grandparent who is not installing anything, not remembering a password, and not creating an Amazon account on your behalf.
One wishlist, two audiences, and a claim system that holds up in the Christmas rush.
Littelist is a two-sided family gift-giving coordinator. Parents get an authenticated dashboard — multiple children with avatars and birthdays, multi-occasion lists, four item types (a specific product, an open theme like “art supplies,” a group-funded bigger gift, an ongoing pot like summer camp), a structured gifting guide, and a coordination view that shows live claim state. Gifters get a magic-link session with no account and no password — a filterable list, one-tap claim/unclaim, multi-claim themes for group-gift coordination, and a personal dashboard across every family they're buying for. Daily reminders fire in plain English: “3 items still unclaimed, 7 days to go.”
Built with- Next.js 16
- Supabase (RLS · RPC)
- React 19 · Server Actions
- Upstash Redis
- SendGrid + React Email
- Vercel Cron
Preview panels will live here once the full case study is authored.
Three decisions shaped the product more than anything else.
The data model comes from how gift-giving actually breaks.
Most wishlist apps model a list as rows of products. That covers maybe a third of how families actually gift. So Littelist has four item types: *specific* (a named product, one claim, done), *theme* (an open category like “art supplies” — multiple gifters, different items), *shared_gift* (group funding toward one bigger thing), and *fund* (an ongoing pot, like summer camp or a 529). Each type has its own claim semantics, encoded in the schema, not the UI.
A second pattern came from a smaller failure mode: a gifter clicks out to Amazon, gets distracted, never comes back. Now the item sits claimed for days, blocking other gifters. So localStorage records the intent on click, and when the tab becomes visible again, the app prompts — *still getting this?* — before letting the hold persist. Small feature. Catches the failure mode that kills coordination apps.
One identity, two auth paths.
The hardest modelling problem was that the same person might arrive twice. A grandmother clicks a magic link in December and gets a 30-day session to buy a nephew's Lego set. In March, her own kid invites her as a gifter to a birthday list. She's still the same gifter. In August she might sign up as a parent herself, managing her own child's wishlist. Same person, three auth states, one identity.
Rather than branch the codebase on “auth user vs. session user,” every gifter — authenticated or not — gets a row in a single gifters table. A Postgres function, get_participant(), checks auth.uid() first and falls back to the session token, returning one unified Participant type. Email changes sync between auth.users and gifters. A permission matrix collapses into a single identity lookup.
- 1 Race-safe claims in Postgres, not the app. Two aunts hit “claim” on the last Lego set in the same second; only one should win. create_claim_safe() is a SECURITY DEFINER function — it checks is_closed, inspects theme_mode, and inserts in a single transaction. Correctness at the database boundary, where it's guaranteed.
- 2 RLS is the permission model. Parents see only their own recipients; gifters see only lists they've been invited to — enforced by Postgres policies, not application middleware. A bug in a route handler can't leak data. The database refuses.
- 3 Reminders deduped at the DB, not the sender. A sent_reminders table keyed by (gifter, list, days_before) means a cron retry can't double-send. The product's value proposition dies if reminders arrive twice or not at all — so the guarantee lives in the schema.
Push the hard guarantees down the stack.
The pattern keeps showing up. Authorisation in RLS. Transactional claim logic in Postgres functions. Rate limits in Upstash Redis so a bad actor can't exhaust one Vercel region. Email retries with exponential backoff, with retryable vs. permanent errors distinguished at the transport layer. The application code — Next.js route handlers, React server components — is thinner and safer because the hard guarantees live closer to the data.
The payoff is a small client bundle, no client state library, and a clear answer to “where does this data come from?” — one line per page. Server components read Supabase directly; mutations are server actions; anything transactional is a Postgres function. Redux, Zustand, tRPC: none of it turned out to be necessary.
Live at littelist.com since December 2025. Two auth models unified on one identity table. Claims, reminders, and rate limits all enforced at the data layer. The concept had been brewing for years — this is the version that finally shipped.
§ Stack and specifics for the builders in the room
Sailing Saugatuck
A family sailing charter with a web presence designed like an Airbnb listing rather than a tour-operator template — #1 outdoor thing to do in Saugatuck on TripAdvisor, 440 five-star Google reviews, running since 2019.
Start a project
No deck, no commitment. Just a conversation about what's going on.