
Rizzl
Rizzl had a business model and no product. I built all three apps and shipped them to production in three months.
The problem
An early-stage creator-marketing startup backed by the Indian School of Business, connecting brands with Instagram creators.
Creator marketing runs on trust in the numbers. A brand pays for engagement it cannot see, and a creator gets paid on a figure they did not calculate. Rizzl needed to sit in the middle and be believable to both — which meant pulling engagement metrics straight from Instagram rather than taking anyone's word, and computing payouts the same way every time. Doing that naively means hammering the Instagram Graph API and the database on every screen load.
The founders needed the whole platform, not a piece of it: an app creators install, a portal brands log into, and an admin panel the team runs campaigns from. All three had to share one backend, one identity system, and one payout ledger.
- One engineer, three client applications, three months to launch.
- Instagram Graph API rate limits and short-lived tokens sit on the critical path of every metric shown.
- Money is involved — payout maths has to be reproducible and auditable, not eventually consistent.
- Pre-revenue startup: infrastructure had to stay on a small AWS footprint.
What I built
React Native app for iOS and Android — Instagram sign-in, campaign discovery, submission flow, and earnings view.
Web app where brands create campaigns, set engagement targets, and track creator performance against them.
Internal tool for the Rizzl team to approve creators, resolve payout disputes, and run campaign operations.
Scheduled service that reads cached engagement metrics, computes creator credits on a fixed rule set, and writes an auditable ledger entry per cycle.
AWS EC2 + RDS deployment, database migration pipeline via Prisma, and the release process the team still uses.
How it's built
Three thin clients talk to one Express API. The API owns identity and business rules; Postgres owns the truth about campaigns and money. Redis sits between the API and the Instagram Graph API so that engagement numbers are fetched on a schedule, not on page load. A cron worker turns those cached metrics into ledger entries.
Decisions & trade-offs
PostgreSQL + Prisma over MongoDB
Every other service I'd built for early-stage clients used MongoDB, and it would have been faster to start with. But this schema holds money.
- ·MongoDB — fastest to iterate while the payout model was still changing
- ▸PostgreSQL + Prisma — real transactions, real constraints, versioned migrations
chose PostgreSQL with Prisma — A payout ledger needs foreign keys and transactions, not application-level discipline. Prisma's migration history also meant the schema changes were reviewable — useful when I was the only person who could review them.
The first month was slower. The payout model changed three times and each change was a migration instead of just writing a different shape of document. I'd make the same call again, but it cost real days early on.
Cache engagement metrics instead of fetching them live
The obvious build fetches a creator's Instagram stats when a screen opens. That breaks immediately — Graph API rate limits are per-token, and a campaign screen showing 40 creators means 40 calls.
- ·Fetch on demand with a short TTL
- ·Refresh on a schedule into Redis and always read from cache
- ·Store every metric snapshot in Postgres and query the latest
chose Scheduled refresh into Redis; the API only ever reads cache — It decouples the read path from Instagram entirely. Screens stay fast and stay up even when the Graph API is throttling or a token needs refreshing.
Numbers on screen are as fresh as the last refresh cycle, not live. Creators occasionally ask why a post from an hour ago isn't counted yet. Worth it — the alternative is a product that gets slower as it grows.
Instagram OAuth for identity, not just for data
Anyone can type in a handle. If Rizzl pays out on self-reported follower counts, it has a fraud problem on day one.
- ·Manual verification by the Rizzl team
- ▸Instagram OAuth — the creator proves ownership by logging in
chose Instagram OAuth, with access tokens encrypted at rest — It makes handle ownership provable and gives access to real engagement data in the same step. Manual review doesn't scale past the first hundred creators.
It put Rizzl on Meta's platform timeline — app review, permission scopes, and token expiry all became our problem. It also means a creator who revokes access silently breaks their own payouts, which needed its own handling.
One backend for three clients
Mobile app, brand portal, and admin panel have genuinely different needs. The textbook answer is a service per surface or a BFF per client.
- ·A service per client surface
- ▸One Express API with role-scoped routes
chose One API, roles enforced at the route layer — One engineer cannot operate three deployments. A single API meant one auth implementation, one migration path, one thing to debug at 2am.
The API does too much and the admin routes are the messiest part of the codebase. This is the first thing that should be split when Rizzl hires a second backend engineer — and I flagged it as such at handover.
Hard problems
Instagram tokens expire and take payouts down with them
- symptom
- Long-lived Instagram tokens still expire, and they expire per creator, silently. A creator who onboarded 60 days ago would just stop accruing engagement — no error, no signal, until they asked why their earnings had flatlined.
- diagnosis
- Traced a creator's missing credits back through the payout worker and found the metric refresh had been failing for that single account for weeks. The worker was catching per-creator errors so one bad token wouldn't kill the whole batch — which was right, but it meant failures were invisible.
- fix
- Made token state explicit rather than incidental: the refresh worker records the outcome per creator, accounts with failing tokens get flagged, and the app prompts a re-link instead of quietly showing stale numbers. The batch still isolates failures — it just no longer swallows them.
Payout runs had to be re-runnable without double-paying
- symptom
- The payout worker is a scheduled job, and scheduled jobs get retried — by the scheduler, or by me, manually, when something looked wrong. A naive implementation credits a creator twice.
- diagnosis
- Obvious in review before it ever hit production, but it shaped the whole design: any job that writes money has to be safe to run twice.
- fix
- Ledger entries are keyed on (creator, campaign, cycle) so a re-run collides with the existing row instead of appending a second one. Re-running a cycle is a no-op, which made the job safe to retry and safe to operate manually.
Three clients, one release
- symptom
- Mobile app updates ship on Apple and Google's timeline; the web apps ship whenever. A backend change that all three depend on can't assume everyone updated.
- diagnosis
- Hit this the first time a payout response shape changed and older app builds were still in the wild.
- fix
- Backend changes became additive by default — new fields alongside old ones, removals only after the app version that needed them had aged out. Slower, but it stopped the mobile store review queue from being on the critical path for backend work.
Outcomes
Creator mobile app, brand portal, admin panel — all live in production.
Nov 2025 → Jan 2026, single engineer.
App screens read cached metrics only, so Graph API throttling never reaches the user. No before/after benchmark was captured.
Cycles can be safely re-run; ledger entries are keyed to prevent double credits.
Every figure is tagged with how it was arrived at. Nothing here is an estimate dressed up as a measurement.
Stack
“Jayesh took Rizzl from concept to launch by building our complete full-stack mobile app, brand portal, and admin panel from scratch. His backend architecture, payout calculations, and AWS setup are highly scalable, and his speed of execution is incredible.”