Promo codes look trivial until you're refunding a bug that let one code stack a thousand times. A robust promo/referral system is mostly about idempotency, atomic limits, and clean auditability. Here's a design that holds up.
Model codes as immutable rules
A code isn't a string — it's a rule set: eligibility, reward, and constraints.
{
"code": "WELCOME25",
"reward": { "type": "percent", "value": 25 },
"max_redemptions": 5000,
"per_user": 1,
"starts": "2026-01-01",
"expires": "2026-03-01"
}
Redeem atomically
The whole value of a limit is that it can't be exceeded under concurrency. Enforce it in one atomic step:
UPDATE promo
SET remaining = remaining - 1
WHERE code = $1 AND remaining > 0
RETURNING id;
No returned row means the code is exhausted — no race, no oversell. Pair it with a unique (code, user_id) constraint to enforce per_user.
Real-world reference
Consumer sites that lean on promo-driven acquisition are a good model for the redemption UX. A promo page like vox casino shows how a code, its terms, and its call-to-action are presented so users understand exactly what they're claiming — worth studying before you design your own redemption screen.
Audit everything
Every redemption writes an immutable ledger row (code, user, ts, reward_granted). When finance asks why a campaign cost what it did, the answer is one query — not a forensic reconstruction.
Takeaway
Codes as immutable rules, atomic decrement for limits, unique constraints for per-user caps, and an append-only ledger. That's a promo system you can run a real budget through.
Top comments (0)