Personal Project
RefundMate
Shipped a live AI complaint-letter generator for Australian consumers — describe a problem in plain language, get a formal letter grounded in Australian Consumer Law
Year
2026
Role
Sole Developer
Duration
1 weeks
Read Time
5 min read

RefundMate
An AI-powered complaint letter generator for Australian consumers. You describe what went wrong in your own words — a dead washing machine, a telco that won't fix your connection, a declined insurance claim — and RefundMate drafts a formal complaint letter grounded in Australian Consumer Law, addressed to the business and citing the correct escalation authority if they don't respond.
Project Overview
Most people are entitled to a remedy under the Australian Consumer Law but don't know how to ask for one. The gap isn't legal complexity — it's tone and framing. A letter that cites the consumer guarantees in the ACL and names the relevant ombudsman gets treated differently from an angry email.
RefundMate closes that gap with the simplest possible flow: four form fields, one API call, one letter. No document uploads, no chat interface, no account dashboard. The deliberate constraint was to build the smallest product that proves the value — describe a problem, get a letter — and ship it live.
The Challenge
- Legal grounding without hallucination: An LLM asked to "write a complaint letter" will happily invent statute references. The legal basis for each complaint category had to come from structured data, not from the model.
- The right authority for the right complaint: Australia routes consumer disputes to different bodies — the TIO for telcos, AFCA for banks and insurers, state energy ombudsmen, the ACCC and fair trading offices for retail. The letter and the UI's escalation link both need to point at the same, correct body.
- A free tier without a database: The app has no persistent storage at all — letters are generated, copied, downloaded, and gone. But a per-account usage cap still needed to live somewhere durable.
Technical Architecture
The core design decision is a single source of truth for the legal layer. Each complaint category maps to an authority object holding the escalation body, its complaint URL, and the specific consumer-rights basis the AI should cite. The same module is imported by the server route (to build the system prompt) and the result page (to render the escalation link) — so the letter and the UI can never disagree:
// Maps a complaint category to the correct Australian escalation authority.
// Shared between the server-side AI prompt and the client-side result page.
export const AUTHORITIES: Record<Category, Authority> = {
"Retail/Product": {
name: "ACCC / state fair trading",
url: "https://www.accc.gov.au/consumers/...",
rightsBasis:
"the consumer guarantees in the Australian Consumer Law (Schedule 2 " +
"of the Competition and Consumer Act 2010 (Cth)), particularly the " +
"guarantees as to acceptable quality (s 54) and fitness for purpose (s 55)",
},
Telecommunications: {
name: "TIO",
url: "https://www.tio.com.au/complaints",
rightsBasis:
"the consumer guarantees in the Australian Consumer Law and the " +
"Telecommunications Consumer Protections (TCP) Code",
},
// ... Banking/Finance → AFCA, Energy → state ombudsman, Insurance → AFCA ...
};
The rest of the stack is intentionally thin: Next.js App Router, one API route that calls OpenAI with the injected rights basis, Clerk for auth, and jsPDF for client-side PDF export. There is no database — the free-tier counter lives in Clerk's privateMetadata, which is server-only and updated atomically per generation.
Key Features
1. Plain-Language to Formal Letter
What it does: Four inputs — what happened, who it's about, what outcome you want, and a category — become a formal complaint letter citing the specific consumer guarantees that apply. The user never has to know what "s 54 acceptable quality" means; the letter does.
2. Correct Escalation Path
What it does: Every result includes a direct link to the body with actual jurisdiction over the complaint — TIO, AFCA, a state energy ombudsman, or the ACCC — with a one-line explanation of when to escalate. Because the mapping is shared code, the link always matches the authority named in the letter.
3. Free Tier Without a Database
What it does: Letter generation requires a free account (Google OAuth or email via Clerk). Each account gets 5 letters for life, tracked in Clerk's server-side privateMetadata — usage enforcement with zero storage infrastructure. A plan: "pro" flag on the user is the pre-built hook for a future Stripe paywall.
4. Abuse Protection
What it does: The generation endpoint is rate-limited with a per-user sliding window (10/minute, 40/hour), unauthenticated probes share an IP bucket, and every form field has a server-enforced length cap shared with the client-side form.
Where It Stands
RefundMate is live at refundmate.com.au — anyone can sign up and generate real letters today. Stripe payments, letter history, and email sending are deliberately not built yet: this is the smallest version that proves the idea, shipped so real usage can decide what comes next.
Lessons Learned
What Went Well
- Putting the legal citations in structured data instead of trusting the model eliminated the scariest failure mode — a letter citing law that doesn't exist — without any output-validation machinery
- Clerk
privateMetadataas the usage store meant shipping a gated free tier with literally no database, no migrations, and nothing to back up - Scoping ruthlessly (no history, no payments, no email) got the product from first commit to a live domain in about a week
What I'd Do Differently
- The in-memory rate limiter is fully effective on a single long-lived server but only per-instance on serverless — it should be swapped for a Redis/Upstash-backed store before the app needs to scale horizontally
- Shipping with zero persistence keeps the stack trivially simple, but it also means no letter history — probably the first thing real users will ask for, so the no-database constraint will likely be revisited before the paywall is
Interested in similar results?
Let's discuss how I can help bring your project to life with the same attention to detail.