Two contracts, eighteen months apart. First a tool, then the system around it.
Two contracts for the same Brisbane car-subscription business — first a tool that turned a one-hour morning task into fifteen minutes, then an automated system with approval gates, a full audit trail, and zero wrong-recipient sends since launch.
- — Role
- Solo product engineer
- — Year
- 2024–2026
- — Duration
- Two engagements, eighteen months apart
- — Stack
- React + Vite · Supabase (Postgres + Edge Functions) · Xero API · JustCall SMS · Mantine · Playwright · Sentry · Netlify
-
0
— wrong-recipient sends since launch
-
~15 min
— morning review, end-to-end
-
11
— categorised audit outcomes per customer per day
The business problem
ClickMe Cars rents vehicles on weekly subscriptions. When customers fall behind on payments, the right SMS at the right time — friendly on day one, firm on day four, formal on day six — is the difference between a paid invoice and a written-off rental. The wrong message at the wrong time (a “police report filed” SMS to a customer who paid yesterday) is the difference between a renewal and a complaint.
Either way, the messaging had to happen every business day, against hundreds of live invoices, without occupying a staff member for half the morning.
First contract — get the messages going out
A web app the office manager could open each morning to draft and send the day’s reminders.
The operator exported overdue invoices from Xero as CSV and dropped them into the app. The app parsed the rows, stripped duplicates, computed days overdue, and drafted the appropriate SMS for each customer using a debt-ladder of templates (debt1 through debt5). Drafts rendered in a review grid; the operator scanned them, edited where needed, and dispatched via JustCall. A history panel showed what had been sent to whom.
Daily messaging went from a one-hour copy-paste exercise to roughly fifteen minutes. The business operated on this for over a year.
Where it hit its ceiling
Naming the limits honestly was the start of the second engagement, not the end of the first:
- The CSV step was a daily friction point and a source of stale data.
- Suppressions (customers who’d just paid, customers on holiday, customers in dispute) lived in the operator’s head and on Post-it notes.
- There was no audit trail — no way to answer “why didn’t customer X get a message yesterday?”
- Two staff couldn’t share the workload without overwriting each other.
- The most serious messages (police-warning copy) were one click away from an accidental send.
When ClickMe Cars wanted to grow the fleet without growing the admin team, those ceilings became the brief for the next engagement.
Second contract — make it run itself
Replace the daily workflow with an automated system that pulls live data from Xero, drafts every message overnight, surfaces a single morning review screen, and won’t send anything dangerous without explicit approval.
Three non-negotiables drove the design — each one a direct read of how the business actually runs:
- Nothing wrong goes out. A wrong-recipient or wrong-day send damages a customer relationship that took months to build.
- The morning ritual stays under fifteen minutes, even as the customer base grows.
- Every decision is auditable — when finance asks why we did or didn’t message someone, the answer has to be in the system, not in someone’s memory.
What carried over from the first build
Several things from the first contract worked well enough that they were preserved verbatim. There’s no honest case for rewriting code the team already trusts:
- The debt ladder copy and cadence (
debt1→debt5, plus the separate toll-reminder track). - Points and payment-plan customer conventions (
^Namefor points customers,Name.for payment plans) — already encoded in how the office named contacts in Xero, kept as first-class flags in the new system. - Operator-in-the-loop dispatch. Drafts are still drafts until a human approves. The system never sends without a click; it just makes that click trivial.
- Decimal.js for currency, Sentry for error reporting, JustCall as the SMS provider — proven choices, no reason to swap.
Safe by default
The most important class of decisions in the system. None of these are visible until they save you.
- Approval gates on the most serious messages. Police-warning messages (
debt4,debt5) cannot be auto-sent. They land in a separate approval queue. “Approve all” on the review screen deliberately excludes them — a staff member has to consciously read and approve each one. - Net-credit detection. If a customer’s outstanding credits exceed their unpaid invoices, the system silently skips them. Nobody on credit gets a debt reminder.
- Idempotency and rate-limiting against external APIs. Both Xero and JustCall calls are rate-limited and retry-aware. Message writes are deduplicated at the (customer, template, episode) level, so a retry, a cron overlap, or a manual re-run physically cannot double-send. Phone numbers are revalidated against Xero on the send path — a typo on the way out gets blocked before it reaches the carrier.
- An environment-flippable dry-run kill switch. A single environment variable disables every outbound SMS without a code change or deploy. The team used this during onboarding to stage a full week of drafts before flipping the switch with no risk and no rollback plan needed.
- Public holiday and business-day intelligence. The escalation ladder counts business days only — Monday to Thursday, skipping Queensland public holidays. Easter, Christmas-observed-on-Monday, and other moving holidays are computed correctly rather than hard-coded. Nobody gets a “return the car by 2pm tomorrow” message on Good Friday.
- A two-layer security model with a domain allowlist. Sign-in is restricted to
@clickmecars.com.auGoogle accounts both at the authentication hook and at the database (Row-Level Security on every table). Even a stolen API key can’t read customer data unless the calling user is on the allowlist.
Honest about what it did
If the system can’t explain itself after the fact, neither can the people running it.
- A complete audit log of every decision the system makes. Eleven categorised outcomes — sent, suppressed, business customer skipped, deduped, awaiting approval, no rule matched, validation error, and so on — captured against every customer, every day, with the reason recorded as structured data.
- Episode reconciliation. The system opens and closes “debt streaks” so it knows when a customer has paid off and a new falling-behind is genuinely new — not a confused re-send against an old streak.
- A 30-day retention policy on Xero snapshots, with snapshot-based historic replay so a past day can be re-evaluated for debugging without touching live Xero.
Built to fit the morning, not the diagram
The system has to land in an actual office, on an actual Tuesday, with an actual coffee.
- Live Xero sync, on a schedule. The CSV step is gone. Every night the system reconciles invoices, credit notes, and overpayments directly from Xero and stores an immutable daily snapshot. The team opens to a finished review screen, not a blank one.
- A suppression workflow that mirrors how the office actually runs. Each morning the team works through a short checklist — check the bank feed, check Stripe, check JustCall replies, check the support inbox — and adds suppressions as they find them. Suppressions are time-bounded, category-scoped (rental vs. toll vs. admin fee), and tagged with the source that raised them, so an incoming payment can mute a customer for the rest of the week without anyone having to remember to re-enable them.
- UX details that pay back daily. The review screen virtualises long lists, persists every filter in the URL (a colleague can be sent a link to exactly what you’re looking at), shows loading/empty/error states explicitly rather than blank screens, and gives every action a confirmation toast. The drag-to-reorder suppression checklist syncs its order across all users from the database, not from one person’s browser.
There are a further twenty or so smaller considerations — business-customer auto-skip, separate late_payment invoice handling, conditional admin-fee display, CAS-protected approval claims so two staff can’t approve the same message — but they all serve the same three non-negotiables: nothing wrong goes out, nothing right gets missed, and everything can be explained after the fact.
Engineering rigour
End-to-end Playwright tests cover the critical flows (sign-in, suppression entry, approval, settings). Unit tests cover the rules engine, holiday math, invoice classification, and templating. An integration test runs the entire daily pipeline against a real database to catch any drift between code and schema. TypeScript strict mode is on across both the frontend and the edge-function backend, with the build refusing to merge on unused variables. Database changes go through versioned migrations only.
Deployment is fully automated — a push to main ships the frontend through Netlify and runs database migrations against Supabase. A short, written smoke-test playbook covers the few things automation can’t verify (an inbound SMS actually arrives, the cron actually fires at the right local time).
Outcomes
Figures marked in brackets are awaiting ClickMe Cars sign-off before publication.
- Morning review time dropped from ~15 minutes of CSV-handling and copy-checking to [~X minutes] of pure approval clicks.
- Operator capacity grew from one daily user to [N concurrent users] across the office without coordination overhead.
- Wrong-recipient sends since launch: zero, attributable to the dry-run staging, phone revalidation, and approval gates.
- Audit response time for finance/support queries — “why was/wasn’t customer X messaged?” — went from “we’ll have to remember” to under a minute via the audit log.
- Police-warning send approvals are now a deliberate, logged action with named accountability, rather than a checkbox in a grid.
- Recovered revenue per messaging cycle: [$X/month] uplift attributed to consistent, timely escalation across the full customer base.
What this engagement demonstrates
For a small business considering software that touches customers and money, three things tend to matter more than feature count: it has to be safe by default, it has to be honest about what it did, and it has to fit the way the office actually works in the morning rather than the way the diagram says it should.
This second contract was an exercise in building exactly that — taking a working tool that had reached the limits of a manual workflow, and turning it into a system the business can grow into without growing the team behind it.
“We were burning 15 hours a week on debt collection admin. Lachlan built us a bespoke SMS distribution system that cut that by 80% in four days. Four days. It paid for itself before the invoice landed.”