Inspiration
Nicotine pouches went from niche to everywhere in about three years. They are discreet, flavoured, and easy to use all day, which is exactly why they are hard to stop. On the App Store, pouch users were being served generic smoking apps: cigarettes-per-day counters that don't fit a habit measured in pouches, milligrams and cans. We wanted a coach built for this specific habit, and we wanted to borrow the one mechanic that reliably keeps people coming back every day: a streak you keep alive with a friend.
What it does
- A plan from your own numbers. Pouches per day, strength, what a can costs. ZyQuit builds a taper to zero over the weeks you choose, or a clean stop.
- A limit that moves. Every day has a limit that steps down. Logging is two taps; resisted cravings count too.
- Quit with a partner. Pair with a friend and the streak becomes shared: it grows while you both hold and breaks for both if either goes over. One freeze a week covers a single bad day, yours or your partner's. If your partner slips while the freeze can still save the streak, you get a push so you can step in.
- A warning before the slip. When the next pouch would break the streak and the weekly freeze is already spent, the app says so before you log it.
- A bonsai that grows with you. Days on plan grow a 3D tree you can open full screen and turn with a finger. A slip costs it leaves. It also lives on your Home Screen as a widget.
- Stats that are yours. A 12-week calendar (on plan / over / frozen), cravings beaten, money kept in your local currency, and a card to share your run.
- SOS for the ninety seconds that matter. A timed breathing exercise for when a craving hits.
- No accounts. No sign-up, no email. History stays on the phone. For pairing, only the day's counters travel under a random identifier, plus a push token if you pair and allow notifications.
How we built it
Native SwiftUI app, SwiftData for local history, RealityKit for the 3D bonsai, WidgetKit for the Home Screen tree. The tree is modelled once and baked into 150 growth frames for the widget, the share card and the store screenshots. The partner streak runs on a small Go + Postgres service: the server is the single source of truth for the shared number, the phone is only a cache. Partner pushes go straight to APNs from the same service. Subscriptions and the paywall run on RevenueCat: RevenueCat Paywalls renders the main paywall (copy and layout change without an App Store release), with our own SwiftUI paywall as a fallback. Offering metadata doubles as a remote feature flag. Amplitude for product analytics, Sentry for crashes, both behind one opt-out toggle.
Challenges
- Honest numbers. Every figure on screen (money kept, nicotine avoided, streaks) had to match what the user logged, to the cent. Our QA re-derived them by hand on real devices across multiple days.
- A shared streak across two phones. Partners report at different times and go offline. We show the last complete number with "waiting for your partner's days" instead of flashing a false break.
- A 3D tree without a frozen launch. Sentry caught a 3-second App Hang: the first RealityKit frame on the Today screen landed at the same moment as the paywall sheet. Today now shows a cached picture of the tree, baked behind the loading splash; the live 3D view starts only when you open the tree.
- Shipping to 168 countries. App Review approved 1.0 with zero sales territories set. We caught it on release day and fixed availability.
What we learned
Behaviour change is mostly about removing ambiguity: one number for today, one person who notices, one small visible reward. And "done" means verified on a device, not green in CI.
Built With
- amplitude
- apns
- app-store-connect-api
- docker
- go
- hetzner
- postgresql
- realitykit
- revenuecat
- revenuecat-paywalls
- sentry
- storekit
- swift
- swiftdata
- swiftui
- widgetkit
- xcodegen
Log in or sign up for Devpost to join the conversation.