Yesterday I spent 8 hours building something that has nothing to do with my product's core features. No new UI. No bug fix. No optimization. A referral system.
I'm building TAMSIV, a voice-powered task manager for Android. Solo dev, 660+ commits, 6 months in. I have 12 alpha testers on the Play Store and I need that number to grow — organically.
So I built the full referral loop. Here's how it works, what I learned, and the gotchas you'll hit if you try this in React Native.
The concept
Simple: every user gets a unique referral code. Share it. When someone signs up with it, both get 1 free month of Pro. And it stacks — 10 referrals = 10 months free, queued one after another.
The 3 capture sources (this is where it gets tricky)
The hardest part isn't generating codes. It's capturing them reliably across all the ways someone might arrive at your app.
1. Deep links via the website
When someone visits tamsiv.com/invite/CODE, Next.js redirects to the Play Store with the referral code embedded:
// next.config.ts
async redirects() {
return [{
source: '/invite/:code',
destination: 'https://play.google.com/store/apps/details?id=com.tamsiv&referrer=:code',
permanent: false,
}]
}
2. Android App Links (direct open)
If the app is already installed, tamsiv://invite/CODE opens it directly. This requires:
AndroidManifest.xml:
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="tamsiv.com"
android:pathPrefix="/invite" />
</intent-filter>
Plus an assetlinks.json on your website for verification:
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.tamsiv",
"sha256_cert_fingerprints": ["YOUR_SHA256"]
}
}]
React Navigation linking config:
const linking = {
prefixes: ['tamsiv://', 'https://tamsiv.com'],
config: {
screens: {
Signup: 'invite/:code',
},
},
};
3. Play Store Install Referrer
This is the sneaky-good one. When someone clicks a Play Store link with &referrer=CODE, Android stores that referrer string. Even if they install the app 3 days later, you can still read it.
npm install react-native-play-install-referrer
import { getInstallReferrer } from 'react-native-play-install-referrer';
const referrer = await getInstallReferrer();
// referrer.installReferrer contains your code
This covers the "click link → browse Play Store → install later → open" flow that deep links can't handle.
Stacking rewards
Most referral systems give you one reward and that's it. I wanted rewards to accumulate. Each referral adds 30 days of Pro, queued after the previous one expires.
The logic: when a referral is validated, the backend checks if the user already has an active reward. If yes, the new reward's start date = previous reward's end date. If no, it starts now.
This required rethinking my subscription logic — RevenueCat handles paid subscriptions, but free months from referrals are managed separately in Supabase.
Push notifications in 6 languages
When someone uses your referral code, you get a push notification. In your language. Because TAMSIV supports 6 languages (FR, EN, DE, ES, IT, PT), the backend checks the referrer's language preference before sending via FCM:
// Backend endpoint: POST /api/push/referral-reward
const referrerProfile = await getUserProfile(referrerId);
const lang = referrerProfile.language || 'en';
const messages = {
fr: `${referredName} a utilisé votre code ! 1 mois Pro offert 🎉`,
en: `${referredName} used your code! 1 free month of Pro 🎉`,
// ... 4 more languages
};
await sendPushNotification(referrerProfile.fcm_token, messages[lang]);
The Invite tab
The frontend has a dedicated Invite section in the Social screen: your unique code, a native share button, referral stats (total invites, pending, rewarded), and reward history. All managed by a ReferralService singleton with caching.
By the numbers
- 22 files changed across backend, frontend, website, and Android manifest
- 1,324 lines added
- 1 day of focused work
- 6 languages supported
- 3 capture sources for maximum coverage
What I learned
Android App Links verification is fragile. The
assetlinks.jsonmust be served at/.well-known/assetlinks.jsonwith the exact SHA256 of your signing key. Miss one character and it silently fails.Play Store Install Referrer is underrated. Most tutorials skip it, but it's the only way to capture referrals through delayed installs.
Reward stacking adds real complexity. A simple "give 1 month free" is easy. Queuing multiple rewards requires tracking start dates, end dates, and the relationship between RevenueCat subscriptions and your own reward system.
i18n multiplies everything. 1 push notification message × 6 languages × 2 recipients (referrer + referred) = 12 translation keys. For every user-facing string.
Will it work?
Honestly? No idea. The code is ready, the circuit is complete. Now it's up to the users.
But here's the thing — building the referral system forced me to think about growth for the first time. After 660 commits of pure product work, spending one day on distribution felt uncomfortable. But necessary.
If you're a solo dev reading this: don't wait as long as I did. Build the growth loop early.
TAMSIV is a voice-powered task manager for Android. Try it on the Play Store or visit tamsiv.com.
Follow the journey: GitHub commits in public | Discord
Top comments (0)