The first directory took months. The second took a weekend, because building the first one properly meant everything niche-specific was already a setting: categories, locations, terminology, pricing, the lot.
That is the whole thesis. Nothing is hard-coded that would stop the same code running a wildlife control directory, an HVAC directory and a car detailing directory at once. It does run all three, from one codebase, with no code changes between them.
A directory only becomes a business when it earns, so there are four ways in and all four are built in rather than bolted on: a lead marketplace where local firms pay monthly, per city, for the quote requests that come in; featured placement on the homepage; featured placement inside a city; and ad slots. The limited number of slots per city is doing quiet work there. Scarcity is most of what makes a local listing worth paying for.
The unglamorous half is filling it. Ten empty categories are not a directory, so the import side took the longest: pulling businesses out of Google Places, scraping their own sites for the detail that is missing, running Claude over the result to clean it up, and a CSV importer that survives thousands of rows and near-duplicate names.
Then it has to run without me. Outreach email goes out at a rate that keeps the sending domain alive, bounces are processed back rather than ignored, and a Telegram bot handles approvals and daily summaries from a phone. When a city has no subscribers yet, the system goes and finds businesses to contact instead of waiting to be asked.
Flask, deliberately. It is boring, it is small, and instances of it have been running unattended for years. It starts on SQLite so it can be unzipped and running the same afternoon, and moves to Postgres on one environment variable. The interesting engineering is in the configuration layer, not the framework.




