CRM for a three-person automation studio
11 spreadsheet tabs → 7 tables in one database; live into its fifth month
Internal system. A three-person automation studio ran clients, projects, proposals, invoices and time tracking across eleven Google Sheets tabs, each opened separately, with no links between them. The first CRM version was only a React front end on that same spreadsheet, with one shared PIN instead of accounts; leads landed in the tab automatically from the booking system, and cleaning them up was manual work.
A working preview. Company, products and figures are invented.
How it was before
almost half: duplicates of the same person, an empty row with no name, one test record
junk rows in the leads tab
the booking system, not a person
source of the duplicates
zero after four days
follow-ups on proposals already sent
the spreadsheet itself, 11 tabs with no links between them
the database
one shared PIN for the whole team
login
The system
- 01Single-page app (React 19 + Vite + Tailwind), eight sections at the time of the migration: dashboard, clients, projects, kanban, invoicing, activity log, email and credentials, plus client and project detail views.
- 02Migration from spreadsheet to Postgres: seven tables, six of them joined by keys (client → projects → files, payments, credentials), the seventh an activity log; file storage for contracts and deliverables; named accounts for login instead of a shared code.
- 03A command-line tool (seven commands) the coding agent uses to pull project context itself and push finished work back into that project's file, with no manual copying.
- 04Booking → CRM chain (9 nodes), built in the spreadsheet era: a calendar webhook creates or updates a lead by email address, with score and last-contact fields.
- 05Post-call sequence on the same tab (20 nodes): waits 30 min, splits attended from no-show, sends the matching email and writes the outcome into six new lead columns; repeat sends are dropped by email address plus an already-sent flag.
The one thing we got wrong.
The first Postgres schema went into the live database with allow-everything rules and a public file bucket, so the key sitting in the browser unlocked the whole CRM; the hole was closed the same day, in the third migration.
Before and after
BeforeAfter
where the data lives
11 unlinked spreadsheet tabs
7 tables in one Postgres database
access to the data
allow-everything rules and a public file bucket
authenticated access only, named accounts, the bucket closed
the spreadsheet version
a React front end on Google Sheets, one shared PIN
switched off: the live bundle makes no Sheets calls
project data for the coding agent
copying files by hand
seven command-line commands
our estimate
how long the system has been running
first live deployment 2026-03-07
still responding 2026-08-05, its fifth month