Series · 7 parts Published June 22, 2026

Gift card ledger

A serverless ledger that issues gift cards and store credit, redeems them, and retires them on a schedule — with balances that are always right to the cent. Issuing mints a unique code and an opening balance; redemption is an atomic DynamoDB conditional write, so the same balance can never be spent twice from two tills at once; velocity and value caps stop a leaked code being drained; and a monthly reconciliation ties issued, redeemed, expired, and outstanding liability together. The match and the arithmetic are plain code — no model ever touches a balance. Seven posts on the same system — one diagram at a time — with an engineering reference at the end.

  1. 01

    A gift card ledger on AWS for a few dollars a month

    The whole system on one page — an issue piece, a redeem piece, and the scheduled expiry and reconciliation — plus the one idea everything rests on: an atomic conditional write that makes a balance impossible to spend twice.

  2. 02

    How a gift card gets issued

    Issuing mints a unique, hard-to-guess code and an opening balance in integer cents, writes the card with a strict "only if it does not already exist" condition, and appends the first immutable row to the ledger. One card, one code, one opening balance.

  3. 03

    How a balance gets redeemed

    The centrepiece. A redemption is a single atomic DynamoDB conditional write — subtract the amount only if the balance still covers it — so the same balance can never be spent twice from two tills at once, and a balance can never go negative.

  4. 04

    How fraud caps stop abuse

    Velocity and value caps that stop a leaked code being drained — a per-redemption ceiling, a daily limit, and a count of redemptions in a window — all checked in the same atomic write as the balance, so a flagged card freezes rather than leaks.

  5. 05

    How a card expires and reconciles

    A nightly expiry sweep retires dormant cards per policy and books the leftover balance as breakage; a monthly reconciliation re-derives every balance from the ledger and ties issued, redeemed, expired, and outstanding liability into one figure the owner can trust.

  6. 06

    What the gift card ledger costs

    A line-by-line cost breakdown that sums to about $2.10/month at roughly 300 cards issued and 800 redemptions — where the atomic redemption costs a fraction of a cent — plus what the bill looks like at ten times the volume.

  7. 07

    Engineering reference: the gift card ledger architecture

    The same system drawn purely for engineers: the Lambda inventory, the two schedules, the DynamoDB tables with their key schema and the exact conditional-write and transaction design, S3 and SES, IAM scopes, and the single region.

What is a gift card ledger?
A small serverless system that issues gift cards and store credit, redeems them against balances, and retires dormant ones on a schedule. Issuing mints a unique code and an opening balance; each redemption deducts from that balance with an atomic conditional write, so the figure is always right to the cent and can never be spent twice. Velocity and value caps stop a leaked code being drained, an expiry sweep retires dormant cards per policy, and a monthly reconciliation ties issued, redeemed, expired, and outstanding liability together. No model ever touches a balance.
How much does it cost to run?
About $2.10/month at typical small-business volume (around 300 cards issued and 800 redemptions a month). The fixed cost is dominated by one Secrets Manager secret and continuous DynamoDB backups; the variable cost is a sliver of DynamoDB on-demand writes, a few cents of SES for receipts, and one small Bedrock call a month for the report narrative. The atomic redemption itself — the centrepiece — is a single conditional write that costs a fraction of a cent. At ten times the volume the bill lands around $9 a month.
Which AWS services does it use?
Lambda (Python 3.14, arm64), EventBridge Scheduler (the nightly expiry sweep and the monthly reconciliation report), DynamoDB on-demand with conditional writes and transactions, S3 (versioned, for exports and reports), SES (receipts and the monthly report), SQS with a dead-letter queue, Secrets Manager, CloudWatch Logs (7-day retention), and AWS Budgets. Bedrock (Claude Haiku 4.5) is used once a month, only to write the plain-English narrative on top of the reconciliation numbers. One region (eu-west-2). No API Gateway, no NAT Gateway, no always-on compute.
How does it stop a balance being spent twice?
With an atomic conditional write. A redemption is a single DynamoDB UpdateItem that says, in one indivisible step, "only subtract this amount if the balance is still at least this amount, and write the new balance." If two tills try to spend the same card at the same instant, DynamoDB applies one and rejects the other with a ConditionalCheckFailedException; the rejected till simply re-reads the now-lower balance and tries again. There is no read-then-write gap for a race to slip through, so a balance can never go negative or be spent twice.
Does it use AI?
Barely, and never on a balance. Issuing, redeeming, the fraud caps, the expiry sweep, and every figure in the reconciliation are plain Python arithmetic over integer cents. Bedrock (Claude Haiku 4.5) fires exactly once a month, only to turn the already-computed reconciliation numbers into a short readable narrative for the owner. If Bedrock were switched off entirely, every balance and every total would be identical — only the prose paragraph would be missing.
Can a balance ever be wrong or go negative?
No. Balances are stored as integer cents, never floating-point, so there is no rounding drift. Every redemption is an atomic conditional write guarded by balance >= amount, so a card can never be overspent and can never go negative. Every issue, redemption, and expiry also appends an immutable row to a ledger table, and the monthly reconciliation re-derives each balance from that ledger and checks it against the stored figure — opening liability plus issued, minus redeemed, minus expired, must equal closing liability, or the report flags it.
All posts