Inspiration
Irina Dith coaches German-speaking women who want to lose weight, and she kept answering the same question: how much am I actually allowed to eat? Every tracker her followers tried gave them a number and then made them feel like they were failing it. Portions got smaller, hunger got louder, and by week three most of them quit.
Her own method says the opposite. Eat enough protein, keep moving, stay full. The German slogan is Abnehmen ohne Hunger — lose weight without going hungry. We wanted an app that behaved like that method instead of contradicting it, and one that was built for the people who already trusted her rather than for an anonymous app-store audience.
There was a second reason. Irina had already sold a video course to 443 women through a third-party checkout. Their access lived in someone else's platform, and we wanted to bring it home without asking any of them to pay twice.
What it does
IriFit is a German-language nutrition and coaching app for iPhone and Android.
It computes a daily calorie and macro target from a short onboarding, using Mifflin-St Jeor for basal metabolic rate:
$$\mathrm{BMR} = 10w + 6.25h - 5a - 161$$
where $w$ is body mass in kilograms, $h$ height in centimetres and $a$ age in years. The constant $-161$ is the female term; over 90 % of the audience is women, and we deliberately do not ask for gender, because a body-composition questionnaire is exactly the tone the app is trying to avoid.
Daily need and target follow from an activity factor and a goal factor:
$$\mathrm{TDEE} = \mathrm{BMR} \cdot \mathrm{PAL}, \qquad \mathrm{PAL} \in {1.2,\; 1.375,\; 1.55}$$
$$\mathrm{kcal} = \max\Big(1200,\; \operatorname{round}_{10}\big(\mathrm{TDEE} \cdot g\big)\Big), \qquad g = 0.8 \text{ for weight loss}$$
The floor of 1200 kcal is a product decision, not a rounding artefact. No user can be pushed below it, whatever the arithmetic says. Macros split 23 % protein, 30 % fat, 47 % carbohydrate, which keeps protein high enough to protect muscle during a deficit.
Around that number sits the rest: a food diary with a photo of each meal, saved dishes for the things she cooks every week, a barcode scanner backed by Open Food Facts, nutrition estimation from a photo or a text description, 160 recipes with shopping lists, five video courses with 36 lessons, weight and water tracking, and step counts read from Apple Health or Health Connect. Irina posts to a channel inside the app and answers questions there. A subscription unlocks everything beyond the basic diary.
How we built it
The client is React Native on Expo SDK 54 with expo-router and typed routes, shipping to both stores from one codebase.
Supabase in Frankfurt holds Postgres, auth, storage and Edge Functions. Every
access rule lives in a row-level security policy rather than in the app, so a
modified client cannot read what it should not. Two Edge Functions run with the
service role because they need to decide something the client must not:
claim-legacy matches a signed-in email address against the list of past course
buyers, and stream-token checks entitlement before minting a two-hour signed
playback URL from Cloudflare Stream. Videos are never addressable without that
check.
RevenueCat tracks subscription state across both stores. The Anthropic Claude API estimates nutrition from a meal photo or a typed description; the image is sent only when the user starts the analysis and is not stored. A Next.js admin panel manages content and customers. The marketing site and the customer course area are static files with no build step.
German is the only locale, and all legal text lives in the same JSON file the app reads. The website's privacy and terms pages are generated from it, so the two can never drift apart.
Challenges we ran into
Three App Store rejections, and each one taught us something. Guideline 3.1.1 killed our in-app entry for existing course buyers, because any mechanism that unlocks content outside in-app purchase counts as an alternative payment path. Guideline 4.8 then removed our social sign-in: any third-party login requires a privacy-focused alternative, and ours had been Sign in with Apple, which Apple had flagged separately as broken. Guideline 5.1.1(iv) removed the polite explanation screen we showed before asking for camera access, because a dismissible pre-permission screen is not allowed. Guideline 1.4.1 required that a calorie recommendation cite its sources, so the app now links Mifflin & St Jeor (1990), the German Nutrition Society and the WHO directly next to the number.
A bug that only existed in Release. Sign-in succeeded, the profile query returned 200, and the user stayed on the login screen anyway. The navigation guard did not fire, and only a cold start got you in. Debug builds never showed it. We found it by running a Release build in the simulator, which is now part of the routine.
Two paying customers locked out by the order of two webhooks. A woman paid for a year and cancelled auto-renewal the same week. RevenueCat sent CANCELLATION and EXPIRATION in the same second; the cancellation carried the old trial end date, and whichever arrived last won. Her access read "expired yesterday" while her receipt said "paid until 2027". The fix is a rule the webhook now applies before writing: no event may pull a running subscription into the past, whatever its type says. It is covered by ten cases in a test script, including the one that cost her the access.
Swapping the soundtrack on 24 workout videos. The originals used music we could
not license. Cloudflare Stream caps storage at 1000 minutes, and when the account
is full the upload fails at registration with HTTP 413, not during the transfer,
so the error looks like a network problem. The swap had to run in stages: upload a
batch, repoint the lessons, delete the old files, repeat.
Bringing 443 customers across without a code. With the in-app entry gone, we
built a web course area instead. The browser signs in with a one-time email code,
the same claim-legacy function matches the address against the buyer list, and
the same signed Cloudflare links play the videos. No code entry, no alternative
purchase path, no one paying twice.
Since launch
IriFit went live on both stores on 14 September 2026. Nine days later:
- 1,357 downloads — 906 first-time downloads on the App Store at a 40.3 % product page conversion, 451 device acquisitions on Google Play
- 1,164 accounts. Nine out of ten women who download the app create one
- 403 free trials started, 83 converted to paid — 32 % of the trials that have ended. 141 are still running
- €3,387 gross, 233 active subscriptions
- 258 women have logged 3,562 meals, 108 started the course
- 0.26 % crash rate on Android, 5.00 average rating
The work since launch has not been marketing. It has been reading what the app records and fixing what it shows.
Every screen is instrumented (50,688 events so far), but the events only say where to look. One example: in 1.0.0, every woman who used the food search hit a dead end 1.91 times. We logged the words that failed — 471 distinct ones — and rebuilt the search around word beginnings, typos and German everyday dishes rather than branded products. By 1.0.2 that number was 0.70, a 63 % drop in five days. The food table now holds 307 staples, and the newest 23 of them are literally the words women typed and did not find: Ofenkartoffel, Hühnersuppe, Laugenstange.
The paywall taught us the same lesson. Until 19 September the yearly plan was preselected. Over the first 935 signups, half the women who opened the store sheet with it cancelled (188 purchases against 189 cancellations); with the monthly plan only one in four did (109 against 37). The store dialog shows €69.99, not the €5.83 in the fine print, and that is where she decides. We swapped the preselection and left the yearly plan one tap away.
Version 1.1 shipped during Shipaton and answers sentences women wrote to us: a photo for every meal, saved dishes for the things they cook weekly, recipe names instead of "1 item" in the repeat list, and a redesigned home screen.
Accomplishments that we're proud of
Every release answers something a real user said. "I ate one scoop more" became editing amounts after the fact. "The barcode doesn't know my yoghurt" became custom foods. "I cook the same thing every week" became saved dishes.
Entitlement is decided on the server, every time. There is no client-side flag that says "this user is a subscriber".
There are no advertising SDKs and no ad-network analytics in the app. Step data is read from the device and never uploaded. The web course area needs no cookie banner, because the only thing it stores is the login token the user asked for.
And the 443 women who bought the course years ago still have it, at a URL they can reach from a phone, without an app install and without a second payment.
What we learned
App review is a design constraint, not paperwork at the end. Every rejection we got pointed at a real decision we had made carelessly, and the app is better for having lost the social login and the pre-permission screen.
Test the build you ship. A Debug build is a different program.
Measure before you redesign. We were about to rebuild the food search on instinct; the 471 failed words told us it was word beginnings and German home cooking, not the ranking.
Put entitlement checks where the user cannot reach them, and write down the
operational traps as you hit them. The HTTP 413 on a full Cloudflare account cost
an afternoon the first time and five minutes the second.
What's next for IriFit
Both stores are live, so the next work is inward. Sign in with Apple and Google come back once we can verify the whole flow end to end, and the legacy course claim will run automatically after any login instead of needing the web detour. After that: a web purchase path so Irina can sell directly to her own community, and the part of the funnel we still cannot explain — 580 women completed the entire onboarding and never tapped a plan. We know the number. We do not yet know why, so we shipped two measurements instead of a guess.
Built With
- anthropic-claude
- app-store-connect
- apple-healthkit
- cloudflare-stream
- deno
- eas-build
- expo-notifications
- expo-router
- expo.io
- firebase-cloud-messaging
- health-connect
- hls
- next.js
- open-food-facts
- postgresql
- react
- react-native
- revenuecat
- row-level-security
- supabase
- supabase-edge-functions
- typescript
Log in or sign up for Devpost to join the conversation.