Inspiration
Every habit app sells tracking. StreakParty sells not breaking - built around one moment: the night your 47-day streak is about to die, and the friends watching it.
Tracking is a commodity. Life gets busy, you stop opening the app, the number resets, and you quietly uninstall the evidence. Nobody needs an app to tell them they skipped the gym - what gets people back is somebody noticing. So the product is the party watching your streak, and the save when it's about to break: capped at one per party per 30 days, free for every user, and visible to the whole party. A rescue, not a ransom.
What it does
- Parties - public or private, daily to monthly, with multiple targets per period ("run 3× a week"). Or invert it: a "no more than" party counts the periods you stayed under a line.
- Check-ins - a photo, a note, and mood and energy before and after, plus a metric the creator picks once (km, minutes, dollars, weight × reps) so the leaderboard compares like with like.
- Boosts and nudges - an encouragement quote when a friend is wobbling, a "done mine, your turn" when they're not.
- Realtime chat, a live leaderboard, a momentum ring, calendar and heatmap, and a Discover tab showing the parties your friends are in.
- Invite links that open the app, or its store page if it isn't installed.
- A daily reminder that only fires if you haven't finished yet - the difference between a reminder and a nag.
- Streak Freeze - a missed day, retroactively covered. One per party per 30 days, shown as a snowflake to everyone in the party. You can save your streak; you can't launder it.
Free: 3 parties, 6 people each, 30 days of history and two freezes - enough to actually build a habit. Pro (USD$39.99/yr with a 7-day trial, or USD$6.99/mo): unlimited parties, 50-member parties, full history and 3 freezes a month. Extra freezes come in a USD$2.99 3-pack.
Boosts, nudges, messaging and friends are never gated. They're the growth loop; charging for them would be selling the engine for scrap.
How we built it
A pnpm monorepo around one API contract: a types-only shared/ package, an Express + Prisma 7 + Postgres backend with Socket.io and rotating JWTs on Railway, and an Expo SDK 54 app (expo-router, NativeWind, TanStack Query, Zustand) for Android and iOS.
One rule shaped the billing: RevenueCat is the source of truth for purchases; our backend is the source of truth for entitlements. A webhook writes a subscription row keyed on the entitlement, not the product - three products across two stores all grant pro. A single services/entitlements.ts decides whether you get a fourth party. The App User ID is our own user UUID from the first line of billing code, because a purchase under an anonymous ID grants nobody anything.
Challenges we ran into
A freeze can't just add a day to the counter. You usually notice a missed day by checking in the next day - by which point the old run is already overwritten. So a freeze writes a row marking the missed period satisfied, and the existing streak math recomputes from history without knowing freezes exist.
The reminder had to know who not to notify. Scheduled pushes can't know who already checked in. Instead a sweep claims each user's day with a compare-and-set - where reminder_sent_on != today matches nobody never reminded, because NULL is not true.
The phone loses the response, not the request. Our logs showed 201s for sends the user was told had failed. Every retryable write now carries an idempotency key, and a network error means "no answer", not "it failed".
Realtime died silently while HTTP kept working. socket.io retries a failed transport but gives up for good on a rejected handshake - and with 15-minute access tokens, that was most cold launches.
Accomplishments that we're proud of
- A paywall on loss aversion at its sharpest moment, not a feature checklist - with a freeze honest enough to show the whole party.
- The free grant is two freezes once, not one a month. A monthly refill means you always have one in reserve, and the panic the paywall rests on never arrives.
- Entitlements enforced server-side from the first billing commit. A patched client gets a nicer paywall and zero extra parties.
What we learned
- Key entitlements, not products. A new plan or price never touches gating code.
- A price experiment isn't a dashboard toggle with a custom paywall. Experiments measure customers marked exposed, and a hand-built paywall exposes nobody until it sends that event itself. Switched on, ours would have run for weeks reporting nothing while looking configured.
- Webhooks arrive twice, and out of order. Dedupe on event ID and compare timestamps, or a late cancellation overwrites the renewal after it.
- Design the money model against the app you built. An inventory of what existed produced the freeze; a roadmap would have produced "unlock premium themes".
What's next for StreakParty
- Party Pass - one host pays to unlock a bigger party, full history and a shared freeze pool for everyone in it. The organiser cares most and will subsidise the rest - and the purchase is itself an acquisition loop.
- Insights - paying back the mood and energy data: "Morning Run lifts your energy 2.1 → 4.0."
- Party themes.
Built With
- app-store
- eas
- expo-router
- expo.io
- express.js
- google-play
- jwt
- nativewind
- node.js
- oauth
- onesignal
- postgresql
- prisma
- railway
- react
- react-native
- revenuecat
- socket.io
- tailwindcss
- tanstack-query
- typescript
- zustand
Log in or sign up for Devpost to join the conversation.