PayPolka - Estimate, Invoice, Get Paid
I built an estimating and invoicing app for small service businesses: Rails 8, SQLite, one small server, live at paypolka.com.
Visit the live site →Building your own invoicing app is a classic developer mistake. It belongs right next to building your own CRM (that’s next). It’s mostly a solved problem, the market is crowded, and every year someone warns a new batch of developers not to do it. I did it anyway.
Two reasons. One, because I can and it’s fun. Two, because I wasn’t happy with any of them and now I can make it whatever I want it to be. Bonus, AI does most of the work, I just have to do product management, my specialty.
My goal
I wanted an estimating and invoicing system for small service businesses: contractors, freelancers, consultants, small studios. A client signs off on an estimate through a public link. Once accepted, the customer can be sent a partial invoice for a deposit. Additional progress payments can be invoiced as desired. Once the work is complete, the final invoice can be sent to close the project. Payments can be made via card, ACH, Cash App Pay, or plain old cash and check. I also wanted a simple recurring invoices workflow, automatic reminders, late fee assessments, and invoice summaries by month, quarter, and client. I wanted to be able to add a user or two to the system to help me manage invoices.
The name
There is a running joke on X about buying domains and never finishing projects. I’m guilty of this also, but .com domains are hard. Nearly every conceivable name one can come up with is likely taken. I don’t really settle on a name until the .com domain is “workable”. After some AI brainstorming, paypolka.com seemed workable. So paypolka it is. Made a logo and an icon in Affinity. Nice and clean like I like it.
LOGO
ICON
Non-negotiables
Tenants connect their own Stripe account with their own API keys. I skipped Stripe Connect on purpose, so there’s no platform account sitting between a business and its money. PayPolka never holds a dollar and doesn’t take a cut. I have to admit this is tempting though and I see why companies do it.
The entire app has to be cheap to host and maintain. I don’t have the resources to manage expensive, hard to host, hard to maintain systems. All maintenance and upkeep will be delegated to an AI agent for triage.
The stack
Rails 8.1, Hotwire, Tailwind, and SQLite. In production. Postgres people might boo at me on this, but Rails 8 made SQLite a legitimate production database for a single-server app, and this is deliberately a single-server app. Litestream streams every change to Cloudflare R2 for backup. Solid Queue, Solid Cache, and Solid Cable handle jobs, caching, and websockets, so Redis never enters the picture. Simple.
Sentry watches for errors. Healthchecks.io watches uptime.
The whole thing deploys with Kamal to one small Linode box. PDFs come from Prawn. Email goes out through Resend’s HTTP API. There’s also zero React.
The missing pieces are the point. Hosting is one box and a backup stream, without a managed-database line item on the bill.
The process
The repo runs on what I am calling the technical constitution. Before any session, the agent reads five docs. CLAUDE.md/AGENTS.md sets the ground rules: the stack, the coding conventions, and the tenant-scoping rules every query has to follow. PRODUCT.md defines the product and its vocabulary. DECISIONS.md is a numbered log of every architectural call and the reasoning behind it: 31 entries so far, from D001 (SQLite over PostgreSQL) to the rule for how the public payment page handles money it can’t yet match to an invoice. STATUS.md says what’s live and what’s next, at least when I remember to update it. LESSONS.md is the running list of things that bit us. Production errors get their own log outside that list: Sentry and uptime alerts land in an incidents doc for the triage agent to work, and the ones that teach a lasting rule graduate into LESSONS.md.
Documentation is the key between calm and chaos.
Tests
~1,500 RSpec examples across 104 spec files. Request specs are the backbone (49 files): they run the controllers against multi-tenant data from FactoryBot, which catches more than unit tests ever did for me. Then 22 model spec files, 13 for services, 11 for mailers. Even the PDFs get tested; pdf-inspector reads the Prawn output back and checks that the invoice says what it should.
Nothing merges until bin/ci passes on my machine: RuboCop, three security audits, then the full suite.
Security
Multi-tenancy is the scary part of a shared-database app, so it gets the most paranoia. Every tenant-owned query goes through the current account, which Rack middleware resolves before the request even reaches a controller. Rules like that drift unless something enforces them, so there’s a spec that greps the app code for unscoped lookups on tenant-owned models and fails the suite if it finds one. The only exceptions live on an explicit allowlist: public pages that look up by random share token, and the staff console.
Tenant Stripe keys are encrypted at rest. Each tenant has its own webhook endpoint, and every incoming webhook is signature-verified against that tenant’s own secret before it’s processed. Public invoice and estimate links are random share tokens, not guessable IDs.
CI runs Brakeman (set to fail on any warning), bundler-audit against a freshly updated advisory database, and an importmap audit for the JavaScript dependencies. Sign-in is Rails 8’s built-in auth with bcrypt, email verification, and opt-in email-OTP two-factor with recovery codes.
The timeline
First commit was April 15, 2026: clients, invoices, the core loop. Stripe Checkout was working in test mode two weeks later. First production deploy was May 6, three weeks from empty repo, and by then the app already had recurring invoices, CSV client import, and email two-factor. Multi-user accounts landed two days after that. In June I ripped time tracking and project management back out (that half of the app wants to be its own product), and the public payment page came in July. As of late July it sits at 162 commits (merges included). AI built nearly 100% of the code.
PayPolka is live at paypolka.com. Sign up during the beta and the $5 rate is yours for as long as you keep the subscription. If you want a free trial email me and I’m happy to oblige.