I was going to buy a CRM.
Not even a fancy one. A hundred-row Airtable, maybe. Something to hold the operator leads I’d scraped for Freebo so I could work them without the “which spreadsheet was it in again” dance.
I didn’t. I built a thing instead. And a day later I typed this line into a chat with Claude:
A good system is one you use. A system you use is one you build.
— Me, mid-sentence, surprised I meant it
That’s the whole post. The rest is just how I know it’s true.
The CRM gravity well
Every time I’m about to work a batch of outbound, there’s a tiny decision point. Where do the leads live? Who’s touched which one? What did they say? When should I follow up?
The default answer is “a tool.” Attio. Pipedrive. HubSpot. Notion with the sales template. Something branded, polished, and twenty-nine dollars a month per seat, with a dashboard and a logo on the login page.
I’ve gone that direction a dozen times. I know what happens. The first week, I live in the tool. The second week, I live in a Google Doc that mirrors what’s in the tool. The third week, the tool is a graveyard of fields I never filled in and views I never opened. Meanwhile the actual conversations are in my inbox, the lead list is a CSV in Downloads, and the “status” is in my head.
The problem isn’t the tool. It’s that I didn’t shape it. I rented it, and the shape I rented didn’t match the shape of my work.
What I built instead
Repo: ~/Projects/freebo-leads. Four things in it:
- A Python scraper that pulls operators from Google Maps and writes raw JSON to disk.
- A loader that normalizes those into an
operatorstable in a local SQLite file. - A site scanner that visits each operator’s website, follows the “book now” link, and figures out which booking vendor they’re on.
- A seven-command CLI called
leads.
That is the whole stack. No Postgres. No Supabase. No hosted anything. The entire system is one file, data/leads.db, and a handful of scripts that mutate it.
For reading, I run Datasette on port 8101 with a metadata.yaml that defines a dozen canned queries. For writing, I use the CLI.

Seven verbs. Each one is maybe twenty lines of stdlib Python. No ORM. No framework. The whole CLI reads like a shell script that happens to be written in Python because Python’s SQLite driver is already there.
Why this is the system I actually use
Here is the test I apply to every tool: do I reach for it unprompted?
Not “do I open it when I remember.” Not “do I open it when the dashboard emails me.” Unprompted. The way I open my terminal in the morning. The way I open Obsidian when a thought shows up.
For a lead list, the thing I reach for is leads next. Not a URL. Not a tab. One command, and the next person worth messaging is on my screen with their website, their booking vendor, their Instagram handle, and the last thing I wrote in a note. That’s a cold reach-out in under five seconds from a cold terminal.
If I’d built this in Notion, I’d be looking at a filter chip. If I’d built it in HubSpot, I’d be looking at a login screen. If I’d built it in Airtable, I’d be looking at a loading spinner. None of those are a sentence I type.
The win isn’t that the system is powerful. The win is that it is shaped exactly like the next action.
The part that doesn’t scale, and why that’s fine
Someone is going to read this and say: a CRM has features your little script doesn’t. Email sync. Call logging. Pipeline stages. Deal values. Reporting. Multi-user. Permissions. Automations. All true. All real. All stuff I don’t need yet.
The leverage of a hand-built system is that it exposes exactly the surface area of the problem I have, and nothing else. Everything on screen is something I use. When I need another verb, I add one. The marginal cost of a new feature is twenty lines, not a Zapier.
When this starts to break, I’ll know. SQLite is a boring, portable, open format. The day I need a real CRM, I’ll pipe it into one. No lock-in, no migration project, no “well, the data was in a custom field.” Everything is already in operators.phone, operators.email, operators.booking_vendor, operators.instagram. Boring columns.
The build cost was a weekend. The maintenance cost is zero so far. The friction cost is one word: leads.
Why the model loved it too
Here’s a smaller detail that mattered more than I expected: because the whole thing is a SQLite file and seven thin verbs, it is a system Claude can also use.
I can hand Claude a session and say “find me the ten operators in the Keys who are on a vendor other than FareHarbor and have an Instagram handle but no email yet,” and the agent writes a one-line SQL query against leads.db and gives me a table. I didn’t build an “AI integration.” I built boring columns, and now any model with a shell can integrate itself.
Try that against a closed CRM. You can’t. You end up begging their API for the fields they decided to expose, in the shape they decided to expose them.
Owning the data isn’t a vibe. It’s a precondition for every automation you haven’t thought of yet.
The real takeaway
The reason “a system you use is one you build” is true isn’t that you’re clever and the SaaS people aren’t. It’s that you know the shape of your work in a way nobody writing a horizontal CRM ever can. The person who built HubSpot had to build the version that works for a thousand different sales teams. The version that works for your sales team, by a mile, is the one you shape against the way you actually move.
If you’re trying to make yourself use a system you don’t love, that is a signal. The signal says: the tool is the wrong shape, and the fix isn’t more discipline. The fix is a Saturday.
Open a SQLite file. Write seven commands. See what happens Monday.