Designing an in-app ad system as finite, dated slot inventory
Instead of a heavy campaign engine, I modeled promotions in Queue Hub as a scarce, geo-zoned slot marketplace — and an ad became just a prioritized organic result.
When I added monetization to Queue Hub — a platform where people join queues at nearby businesses — the obvious move was a "campaign manager": budgets, bids, impressions, targeting rules. I didn't build that. Here's the model I used instead.
The core insight
An ad is just an existing business card, prioritized.
The discovery feed already returns nearby business cards from one API with one shape. A "sponsored" result doesn't need a different component, a different endpoint, or a different data model — it needs to be the same card, pushed to the top, with a Sponsored badge. Once you see promotion as prioritization, most of the complexity disappears.
Slots, not campaigns
So I modeled the inventory directly. A slot is a (geographic zone × category × position × date) tuple — and there are only three positions per category per zone per day. That gives you:
- Scarcity that's honest and legible — a business can see exactly what's available.
- Predictable pricing — each slot carries a price; zones keep a dynamic baseline.
- No overselling — a unique constraint on
(zone, category, position, date)makes double-booking impossible at the database level.
A cron job generates the next 30 days of slots ahead of time. Booking is direct: pick your
dates, see a live preview, pay via Razorpay, done. The whole thing needed three new tables
(zones, ad_slots, ad_bookings) on top of the existing schema — no campaign table at all.
Why this is better for a small platform
- It reuses the discovery UI and API verbatim, so there's almost no new surface to maintain.
- The economics are transparent to both sides — sellers understand "a slot for a day," not an auction.
- It scales by adding zones, not by adding system complexity.
The lesson I keep relearning: before building the powerful general system, ask whether a small, finite, well-constrained model gets you 90% of the value. Here it got 100%.