An automated reporting pipeline and a multi-tenant client portal for a lead generation agency running paid campaigns for insurance and home services brands — where the spend, the leads and the truth all live in different systems.
→ You’re already signed in as Halcyon Insurance Group. Flip identities on the portal and watch what changes — and what you can no longer see.
This session carries t_hal. Ask the API for any tenant’s summary — or for no tenant at all — and see what comes back:
— no request sent yet
The client is a lead generation agency. They run paid campaigns for insurance and home services brands — quote forms, call-tracked leads, booked jobs. The agency isn’t named here at their preference, so take them as the agency throughout. The detail that matters: their numbers could never live inside any one ad platform. Spend sits in the ad accounts, the leads sit in call tracking and each brand’s CRM, and whether a lead turned into a booked job is a third system entirely. No platform holds the whole sentence.
Monthly reporting was a person exporting from several ad accounts, pasting into a spreadsheet, making it presentable, and emailing each client a PDF. Once a month, per client. The PDF was stale the moment it sent, and every mid-month “quick question — how are we doing?” replayed the whole export dance in miniature. Worse: when a brand’s campaigns moved to a new ad account — a rebuild, a migration, a client arriving from a previous agency with half their history behind someone else’s login — the spreadsheet’s lineage quietly broke. Nobody decided to have a reporting problem. It accreted.
Two things pretending to be one. First, a pipeline that pulls spend and performance from every channel account daily, normalises it, and stores it keyed by client, not by ad account — so when a brand’s campaigns move to a new account, the client’s history stays continuous. Second, a portal on top: each of the agency’s clients has their own password and sees their own campaigns, spend, and delivery history, live, whenever they want. The agency gets an admin view across everyone. The monthly report still goes out — it generates itself now — but it’s a bookmark, not the product.
The portal at the top of this page is the shape of that build, with fictional clients and fictional figures. Sign in as each of them. The dashed amber rule on Vantage’s local services campaign marks the week that campaign moved to a new ad account — the history reads as one line, which is the whole point of keying by client.
Multi-tenancy is mostly a data-model problem wearing a UI costume. The dashboard was the quick bit. The actual work is making it structurally impossible for client A to ever receive client B’s row: every table carries a tenant id, every query goes through a layer that refuses to run without one, and there is no getAllCampaigns()for the client-facing side to call by accident. Deliberately boring. The scariest bug in a system like this isn’t a crash — a crash is loud. A cross-tenant leak is silent, and on a roster where two clients sell the same policy or the same roof in the same metro, a client seeing a competitor’s spend is not an “oops” email. So the scope check lives at the query layer, not the page layer, and the 403 in the demo above is the honest shape of it. So is the 400 — a query with no tenant attached doesn’t return everything, it returns nothing.
What I’d do differently: build the delivery log first, not last. It sounds like an afterthought until a client says “we never got June’s report” and the answer is a generation timestamp and the minute their session opened it. That log has ended more disputes than any chart on the board.
The mid-month emails stopped. Nobody asked clients to stop sending them — “how did last month go” is just a question you only ask when you can’t see. What replaced those emails is narrower and better: why is this campaign’s CPA drifting, should budget move off the paused one, what happened in week three. Those are questions an agency actually wants to be asked, because they’re about the work rather than about the paperwork.
And the monthly report stopped being a thing anyone assembles. It still lands on the first, on time, in every client’s inbox — no one opens a spreadsheet to make that happen any more. It’s a reminder that the portal exists, which is about the right amount of importance for a PDF.
Tell me where your reporting lives right now — even if the answer is “four exports and a prayer”. I’ll tell you what the portal version looks like.
Go Pivot Edge · built by Arsalan