Dotty
CompletedInvoice portal for freelancers.

Click image to enlarge
1 / 7
Freelancers send invoices by email, wait for a bank transfer, then write an awkward follow-up when nothing arrives. I built Dotty to replace that.
Client gets an email invite, registers, and sees their own portal with a Pay button. They pay by card. You see the status change. No spreadsheet to update, no email thread to dig through.
When something's overdue, Claude Haiku drafts a reminder based on the invoice details. You read it, tweak it if you want, send in one click. I generate it fresh each time so it doesn't sound like a template — because it kind of is one, and clients can tell.
How I built it
This handles money and client data, so I was careful about a few things.
The Anthropic API key lives in Supabase secrets only. If it were in the browser, anyone could find it and run requests at your cost. So it isn't.
Client role assignment happens on a Postgres trigger at signup. If your email was invited before you registered, the database gives you the client role automatically. I didn't want that logic sitting in the app where it could be bypassed.
Stripe webhooks are verified by signature before anything processes. If the signature doesn't match, I ignore the request. That's how you know a payment confirmation actually came from Stripe.
Unauthorized resources return the same response whether they exist or not. A client can't probe for other clients' invoices by guessing IDs.
The app is a React SPA on app.dotty.ai. The landing page is a separate Astro site on dotty.ai. Both in one Turborepo monorepo with shared config.





