Resources / Inside a custom app

Case study · A build that is live today

Inside a custom app I built

A business with referral partners had a slow leak. Partners kept emailing to ask what had happened to their leads, and commissions were worked out by hand in a spreadsheet at the end of every month. I built them a portal. Partners now sign in and see their own referrals moving in real time, and the commission run happens on its own. This page opens that build right up: what it does, the three genuinely hard problems inside it, exactly what it runs on, and what it costs to keep alive.

The problem worth paying to fix

This was never really about software. It was about partners going quiet.

Before Chasing, guessing, waiting

  • Partners emailed to ask what happened to the person they sent
  • Someone answered those emails by digging through the CRM
  • Commission was calculated by hand, once a month, from a spreadsheet
  • Every partner had a different arrangement, held in someone's head
  • A partner who stopped hearing anything simply stopped referring

After Self-serve, and paid on time

  • Partners sign in and watch their own referrals move through the stages
  • Each card shows the deal value and the commission it will earn them
  • The monthly payout run assembles itself and emails the batch through
  • Each partner's rate is a setting in the CRM, changed without a developer
  • Access is granted or revoked by tagging a contact, and takes effect at once
0"where's my lead?" emails to answer
1 taggrants or revokes a partner's access
Monthlypayout run that fires without anyone

What a partner actually sees

One screen. Their referrals, as cards, in the stage each one has reached. It refreshes itself every couple of minutes, so a partner watching during the day sees things move.

Illustration only. Names, values and stages are invented, and every partner sees strictly their own referrals and nobody else's.

And once a month, it pays them

This is the part that used to be an evening with a spreadsheet. Now nobody starts it and nobody assembles it.

MONTHLY, WITHOUT ANYONE CLICKING ANYTHING 1 It starts itself The CRM's own schedulerfires the run. No outsidetimer to babysit. 2 It gathers Every deal marked paid,matched to the partnerwho referred it. 3 It checks Anyone already settled onthat deal is skipped, sonobody is paid twice. the double-payment guard 4 It closes Each deal stamped,one batch summaryemailed through.
The whole run is unattended. The trigger lives in the CRM rather than in the app, which matters more than it sounds: the business can see the schedule, pause it, or fire it early without needing me. Step three is the one that lets it be trusted with money.

The six layers, and what I chose for each

Every custom app is the same six parts, whether it is two screens or two hundred. Here is the whole thing on one picture, then the anatomy with the actual decision I made for each layer. Read it as a template for reading any quote you are ever given.

A partner in a web browser signs in CLOUDFLARE PAGES free tier, in the client's own account The board static files, served from everywhere at once The rules login · the per-partner filter · the payout run checks the password Password store one-way hashes only, never inside the CRM reads leadsread-only Their CRM the single source of truth Partners and their access Leads and their stages Deal values Commission rates
Six layers, four boxes, no database. The dashed box is everything I had to build and host. Everything on the right they already owned and already paid for, which is why the running cost of this app rounds to nothing. Note the arrow only points one way: the portal reads from the CRM and cannot write to it.
1

The screens

What people see and click. Usually the only part anyone pictures, and usually the smallest part of the work.

In this buildA single board, served as static files from a global network so it loads fast wherever a partner is.
2

The rules

What happens when someone clicks. Who may do what, how the numbers are worked out, what triggers an email. Your business lives here.

In this buildA small piece of code that runs at the network edge next to the screens. No server to patch, nothing sitting idle costing money.
3

The memory

Where the data lives. The most consequential decision in the whole build, because two copies of the truth is a problem you never stop paying for.

In this buildNo new database at all. Their CRM stays the master record, and the portal is a window onto it. Nothing to keep in step.
4

The doorman

Who gets in, and what they may see once they are in. A partner should never reach another partner's numbers by editing the web address.

In this buildEmail and password. Passwords are stored only as a one-way scramble, never as text, and never inside the CRM. Repeated wrong guesses lock the door for fifteen minutes.
5

The connections

Everything it plugs into. In most business apps the connections are the product, and the screens are just how you look at them.

In this buildThe CRM, in both directions: it supplies every lead the board shows, and its own scheduled workflow is what fires the monthly payout run.
6

The home

Where the code actually runs, plus the path that gets new versions onto it safely. Get this wrong and the live app becomes code that exists nowhere else.

In this buildCloudflare Pages on the free tier, in the client's own account, released from a code repository so every version can be rolled back.

The three hard bits

The board itself took days. These three took the rest of the build, and they are the reason it can be trusted with money.

Hard bit one

How do you guarantee a partner can never see another partner's leads?

The filter has to happen on the server, before anything is sent to the browser. Anything filtered in the browser can be undone in the browser.

There was a subtler trap underneath. One person can be both a partner and a customer, and the referral forms write to the same record. So the field that decides who you are is one only the onboarding process can write, never a field a form can touch. Otherwise a partner filling in someone else's referral link could quietly change who the portal thought they were.

Every lead A B B A The rules filter on the server, before it is sent Partner A sees only A Partner B sees only B
The other partner's cards are never sent to the browser at all, so there is nothing to uncover by poking at the page.
Hard bit two

How do you make sure nobody gets paid twice for the same deal?

Each deal carries a stamp recording which partners have already been settled on it. The payout run reads that stamp before it includes anything in a batch.

A list rather than a simple yes or no, because two different partners can legitimately earn on one deal. A single flag would have let the first payment block the second, and the second partner would never have been paid.

June run settles partner A July run settles partner B One paid deal A$9,200 SETTLED SO FAR, AS A LIST partner A ✓ partner B ✓
A single "commission paid" tick box here would have been the obvious build, and it would have quietly underpaid the second partner every time.
Hard bit three

How does the business change the deal without calling me?

Every commercial term lives as a setting in the CRM, not in the code. Each partner's rate sits on their own record, and changing it takes effect on the next load.

This is the difference between an app the business owns and an app it rents from whoever built it. If a rate change needs a developer, you have bought a dependency, not a system.

Partner record COMMISSION RATE 10% typed in by the business, any day read live The app applies it on the very next load No deploy. No developer. No waiting on me.
Every commercial term is wired this way: rates, which partners are active, even which referral links each of them gets.

The rule underneath all three. If a setting is missing, the app stops and says so rather than quietly guessing at a sensible default. Silent guesses are how you find out six weeks later that every payout since has been wrong, with no error ever raised.

What it costs to keep alive

The part everyone expects to be expensive, and almost never is.

Hosting: nothing

Screens and rules both sit on Cloudflare Pages, comfortably inside the free tier at this size. Free until there is real traffic, and cheap after that.

Database: nothing

There isn't one. The CRM they already pay for is the master record. No database bill, no backups to run, and no second version of the truth to reconcile.

Logins: cents

Password hashes and lockout counters live in a small key-value store beside the app. It costs cents a month and keeps auth secrets out of the CRM entirely.

One deliberate choice worth calling out. I hosted it as a Pages project rather than a bare worker, because pointing their own domain at Pages needed a single DNS record. The alternative would have meant moving control of their entire domain to a new provider, which is a real risk to a live website in exchange for nothing. Hosting decisions are rarely about the code.

Two more builds, two different shapes

Not every app looks like that one

Almost every custom build I do falls into one of three shapes. The portal above is the first. Here are the other two, both live, both put together the same way and hosted very differently.

THE PORTAL Person The app CRM People log in and see their own data. THE ENGINE A clock Service no screens Result and an alert Nobody logs in. It just has to run, on its own. THE WINDOW Record untouched Build A page static The data already lives somewhere you trust.
Same six layers every time, wired three different ways. The shape decides the hosting, the cost and most of the build time, which is why it's the first thing worth settling in a conversation.
Shape two — the engine

The app with no screens

A property manager looks after about seventy homes across four states and needed to know if any of them quietly appeared for sale publicly. Every Monday at 5am the app checks each address, matches strictly on the exact street number so it never cries wolf, writes the results to a sheet, and pings the team if anything needs a look.

There is a second, smaller job whose only purpose is to shout if the first one fails to run. An automation nobody is watching is an automation you cannot rely on.

  • ScreensNone
  • RulesA scheduled service on Railway
  • MemoryA spreadsheet they already use
  • AlertsSlack, plus a failure watchdog
  • HomeThe client's own Railway account

A few dollars a month, all in

Shape three — the window

The app that is just a better view

My own business dashboard. Every number I track already lives in one system, so the last thing I wanted was a second copy of it. Instead the data is pulled, rendered into a single self-contained page of charts, and published. No server, no database, nothing to maintain.

Because the page sits on public hosting and shows revenue figures, the whole thing is encrypted behind a password before it is published. Free hosting does not have to mean exposed.

  • ScreensOne static page of charts
  • RulesA build script, run on demand
  • MemoryThe system of record, untouched
  • DoormanPassword, encrypted before publishing
  • HomeStatic hosting, free

Nothing a month

Which shape is yours? If people log in and see their own data, it is a portal. If nobody logs in and it just needs to run on its own, it is an engine. If the data already lives somewhere you trust, it is a window, and you should resist every urge to copy that data into a new database.

The parts I choose between

Across those three builds, this is the shopping list I pick from. Most small business apps need four or five of these rows, not all twelve. Costs are indicative monthly figures in Australian dollars at small business volume, and nearly everything here has a genuinely usable free tier.

Layer What I'd reach for Used in the portal Typical cost
Front end hosting Vercel for anything built in Next.js or React. Cloudflare Pages and Netlify are strong alternatives. GitHub Pages when the site is purely static. Cloudflare Pages $0 to $30
Back end and API Railway for an always-on service in any language. Cloudflare Workers when you want it fast, near the user and cheap. Vercel Functions if the front end already lives there. Render and Fly.io are close cousins of Railway. Cloudflare Worker $0 to $40
Database Supabase is my default when the app needs its own data: a proper Postgres database with logins, file storage and an admin screen included. Neon for Postgres on its own. Or the CRM itself when the app is really a window onto it. None. The CRM is the record $0 to $40
Logins and permissions Supabase Auth if you are already on Supabase. Clerk for a polished sign-up flow out of the box. Auth0 for enterprise requirements. Hand-rolling this is usually a false economy. Hashes in Cloudflare KV $0 to $40
File storage Supabase Storage beside the database. Cloudflare R2 when there is real volume, because it doesn't charge to read files back. Amazon S3 is the incumbent. Not needed $0 to $15
Scheduled jobs Railway cron, Cloudflare Cron Triggers or GitHub Actions on a schedule. When the trigger really belongs to a business process, your CRM's own scheduler is the honest place for it. The CRM's own scheduler $0 to $10
Email and SMS Resend or Postmark for reliable transactional email. Twilio internationally for SMS, or an Australian provider for local numbers and better local delivery. The CRM's own sending $0 to $50
Payments Stripe for almost everything. Never store card details yourself, and never build a form that touches a raw card number. Not needed Per transaction
The AI layer OpenAI, Google Gemini or Anthropic. Whichever you pick, the account and the key belong to your business, so you can see the usage and you can switch. Not needed here Usage-based
Code repository GitHub, connected to the host so that saving the code is what publishes it. This is also your undo button, and the reason a bad release takes a minute to reverse. GitHub, client-owned $0
Secrets The environment variables section of whichever host you chose. Never in the code, never in a spreadsheet, never pasted into a chat. Cloudflare secrets $0
Monitoring Your host's logs, plus Sentry for errors and any uptime pinger. An app with no logs cannot be debugged and cannot be audited. Host logs $0 to $30

Turn logging on at the start. I was once asked to prove whether a client had used a feature that shipped a month earlier. No application logs, no access log from the vendor, nothing. The answer had to be reconstructed from the traces the app had left behind in another system. Logging costs nothing and you will eventually need it.

How I build these

You own everything

Every account, domain, repository and key sits in your business's name, and you get a written map of what connects to what. If you ever want to work with someone else, nothing is holding you hostage.

A human gate on anything that matters

Anything that spends money, sends to a customer or publishes stops for a person first. Automation is there to take the labour, not the judgement.

Loud failures, safe reruns

Missing settings stop the run with a clear message instead of guessing. Anything that imports or syncs can be run twice without duplicating a thing. Every build ships with a way to switch it off.

The number that decides whether to build at all. Take the hours your current manual process burns each week, multiply by what that hour costs you, then by fifty. That is the annual bleed, and it is the only sensible thing to compare a build against. If an app can't beat that number inside a year, the honest answer is not yet.

Wondering what your version looks like?

Tell me the job you are trying to do and I'll tell you honestly whether it is a build like this one, a reconfiguration of software you already own, or something you should simply buy. That conversation is free and it is usually the most useful half hour of the whole project.

Book a free 30-minute consultation

harshan@flowlamina.com