All posts
ProductBackendMarketplacePostgreSQL

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%.