Retainer Project
Executive summary
Agencies sell a fixed block of hours a month and then lose track of it. The account lead answers “how many hours do we have left?” from memory, and the client finds out the retainer ran dry when the invoice lands. V1 puts one number in front of both of them: hours granted, hours drawn, hours remaining, and the date the balance expires.
The core loop
- The lead opens a client and sees this month’s allotment and what is left of it.
- Once a week they type the hours drawn since last time, with a one-line note.
- The remaining balance moves, and the overage flag trips if it goes past zero.
- The client opens their own read-only balance page and reads the same figures.
What v1 contains
- Hours drawn against a monthly allotment. The account lead types one number a week. This is the only figure the client and the agency currently disagree about.
- Client-facing balance page. Read-only, shows granted, drawn, remaining, and the expiry date. Removes the recurring email thread without any write path for the client.
- Overage flagged, not billed. The app says the retainer is spent. What that costs is a contract change, not a feature.
What v1 deliberately leaves out
- Live timers. Nobody disputes elapsed time, they dispute the rollover rule. Timers cost weeks and settle nothing.
- Automatic invoicing. The overage rule has to be settled in the contract before software can bill against it.
The data underneath
Four nouns, and the remaining balance is not one of them.
- client: name, retainer_hours, started_at
- allotment: client_id, hours, granted_at, expires_at
- draw: allotment_id, hours, note, logged_at
- invoice: client_id, cents, period, sent_at
Recommended architecture
A server-rendered app over one relational database, with a weekly summary going out by mail. Remaining hours are computed as the allotment minus the sum of its draws on every read, never stored, because a corrected draw has to move the balance with it and a stored column is exactly how that goes wrong six months in. Assumed: the client reads their balance from a signed link rather than an account, because a second login for someone who visits twice a month is more support surface than it is worth. Outbound mail is the only third party, and it can fail without taking the balance with it.
What could go wrong
- Unused hours: expire, roll over, or partially roll. This decides the schema rather than the screen: a rollover is either a new allotment or a mutated one, and the two do not convert into each other later. Settle it in the contract wording before the first table is written.
- A draw logged after the allotment expires. Late timesheets are normal, so a draw arrives after the month it belongs to has closed. Bind a draw to the allotment rather than to the date it was typed and the problem stops existing.
- The client and the lead reading different numbers. Two views of one balance is how the email thread comes back. Derive both from the same query rather than shipping a client-side copy.
07 sections · cut to fit
Example blueprint · the document you receive