-
-
Unhook — break any habit, one day at a time.
-
-
Dozens of coping tools, one tap away.
-
Dozens of coping tools, one tap away.
-
Guided box breathing, right when a craving hits.
-
A quick riddle to pull your mind off the craving.
-
Every honest day earns discipline points to redeem.
-
Real recovery journeys from real people, cheering you on.
-
Unhook Pro — AI coach, deep trigger analytics, full toolkit.
-
Paywall
Inspiration
Quitting a habit — alcohol, vaping, smoking, caffeine, sugar, anything — rarely fails in the abstract. It fails in one specific moment: a craving hits, willpower runs out, and there's nobody there to talk you through it. Most recovery apps are built around after-the-fact tracking — streak counters, journals, motivational quotes you read when you're already fine. We wanted something that shows up in the moment, not just around it: real, immediate help the second a craving starts, backed by proof that it's actually working over time.
What it does
Unhook is a daily recovery companion built around one idea: break any habit, one day at a time.
- In-the-moment help — tap "I'm Craving," tell it which habit, what set it off, and how long you've got, and it routes you straight into a real coping tool: box breathing, 5-4-3-2-1 grounding, a quick walk, cold water, journaling the urge — or a riddle, an odd-one-out puzzle, or a number puzzle to simply pull your mind away from the craving until it passes.
- A daily pledge — one small, editable promise you make to yourself each day, with a full history you can look back on.
- Real progress, not just streaks — cravings beaten, units avoided, money saved, a full clean-days calendar per habit, and trigger insights.
- Community — read real recovery journeys from other people, react and cheer each other on, and share your own story — a milestone, a hard day, or a quiet win, anonymously if you'd rather.
- Rewards — every honest day earns discipline points, spendable on themes, activity packs, and Pro trials.
- Unhook Pro — an AI recovery coach, deep trigger analytics, and the full activity toolkit, via a real subscription (RevenueCat) with transparent Play Store pricing.
How Unhook brings people back (OneSignal)
People are most likely to give up on a recovery app right after a slip, so that's where Unhook's OneSignal work starts.
Identity and state. Each device logs in to OneSignal with the user's Cognito ID as its External ID, the same ID that RevenueCat and our backend use. It logs out on sign-out and on account deletion, so a shared phone never inherits someone else's messages. Three tags (is_pro, primary_addiction, max_clean_days) refresh on login and after every check-in or slip. Nothing else goes to OneSignal: no craving logs, triggers, journal text or community posts.
Server-sent pushes, triggered by what actually happened. Our AWS Lambda backend calls the OneSignal REST API directly and targets the person by External ID. The REST key lives in AWS SSM, never in the app.
- After a slip, whether it's logged from the craving flow or the daily check-in, an immediate, non-shaming push goes out: "You're still here, and that counts. Tomorrow is a new start." A follow-up is scheduled 24 hours later with
send_after: "How are you feeling today? Checking in after yesterday." - Once a day, an EventBridge job works out "today" in each user's own timezone. It nudges only the people who haven't checked in or haven't made today's pledge.
Four Journeys, live since 11 September:
- Onboarding nurture: new subscribers get a Day 0 welcome, then a Day 3 tip, then a Yes/No branch on a 7-day streak. People who made it get congratulations; everyone else gets a gentle nudge. Each person goes through it once.
- Win-back: free users inactive for 3 days get one soft nudge. Pro users are left out, and nobody gets it again for 14 days.
- Milestone congratulations: fires when
max_clean_daysreaches 7, 30, 90 or 365, with the number filled in by Liquid:🎉 {{max_clean_days}} days clean!Check-ins update the tag right away, so the message arrives the same day. - Pro-trial win-back: free users inactive for 7 days get a trial offer, at most once a month, separate from the 3-day Win-back.
The server owns same-day check-in and slip messages, and the Journeys own the longer arc. Social pushes (mentions, friend requests) go through FCM separately, so OneSignal stays the re-engagement channel.
Results so far (small numbers, but real): 90 new subscribers have entered the onboarding Journey and 66 have completed it. The Day 0 welcome was delivered to 68 of 68 people, with 3 clicks (4.4%). Win-back, Milestone and Pro-trial have reached 4, 2 and 2 people so far. Daily nudges go out every night, and slip pushes go out with their 24-hour follow-ups whenever someone logs a slip.
OneSignal App ID: c5699281-9ad0-4787-9ede-8367a41e6f5f
How we built it
- Frontend: Flutter/Dart, Provider for state management.
- Backend: fully serverless on AWS — SAM/CloudFormation, AppSync GraphQL, DynamoDB in a single-table design, Cognito for auth (including a custom-branded email-OTP flow for both signup and password reset, not Cognito's default).
- Two resolver styles by design: Lambda-routed TypeScript resolvers for anything with real business logic, and direct AppSync JS resolvers straight to DynamoDB for simple, high-frequency reads/writes — keeping the hot paths cheap and the complex ones testable.
- Purchases: RevenueCat (
purchases_flutter+ the native RevenueCat paywall UI), live on Google Play. - Re-engagement: OneSignal, with External ID set to the Cognito ID, three state tags, REST API sends from Lambda (including
send_afterscheduling) and four live Journeys. See How Unhook brings people back above. - Theming: a real design system — Fraunces for display type, Work Sans for UI, three redeemable color themes (Sage, Sunset, Ocean), each with light/dark variants.
Challenges we ran into
- A silent account-deletion bug. The delete-account flow wiped a user's data before reading the email it needed to actually delete their Cognito login — so "deleted" accounts kept working credentials with no data behind them. Found and fixed by tracing the exact order DynamoDB items were read and deleted, then verifying the fix against the live deployed Lambda bundle, not just the source.
- Two backend architectures coexisting. Some GraphQL fields route through Lambda, others go straight to DynamoDB via AppSync JS resolvers. More than once a feature looked "missing" simply because we'd only checked one of the two — a good lesson in verifying against the real deployed system, not assumptions.
- A moving compliance target. Google's Play Billing Library 8.0 mandate landed mid-build, forcing a purchases-SDK major-version bump, a native
Activitybase-class change for the new paywall UI, and a guard so a test API key could never crash a release build. - Privacy vs. usability. For a recovery app specifically, we deliberately kept "wrong password" and "no such account" indistinguishable at sign-in — leaking which emails have accounts is a bigger risk here than in a typical app.
What we learned
That the hardest bugs aren't the ones that crash — they're the ones that silently look like they worked. Every fix above shipped only after we verified it against the real, deployed system, not just the code we'd written. And that a recovery app's UX decisions (like the sign-in error message) carry weight a normal app's don't.
What's next
Deeper trigger-pattern analytics for Pro users, more community moderation tooling, and expanding the activity library based on what users actually reach for most in the moment a craving hits.
Built With
- addiction-recovery
- amazon-web-services
- android
- aws-appsync
- aws-cognito
- aws-lambda
- aws-sam
- cloudformation
- dart
- dynamodb
- flutter
- google-fonts
- google-play-billing
- graphql
- mental-health
- mobile-app
- node.js
- onesignal
- provider
- revenuecat
- serverless
- typescript
Log in or sign up for Devpost to join the conversation.