SHT-01General arrangement

A CRM shaped to your trade, not a login you rent.

Software you rent makes you bend the business to fit it. A custom system does the opposite: your stages, your pricing rules, your documents, your data, in one place your crew can actually use.

SHT-02Live detail · drag it

Drag a corner. The price follows.

This is the planner from a sauna builder's system, running here on demo data. It is the clearest answer to what custom means: no product you can buy has a sauna in it.

Det. 03 · room plannerdemo data · example rate: $50 per sq ft of wall
N01

Walls stay square

Drag one corner and its neighbours move with it. A diagonal wall prices a room nobody can frame, so the geometry refuses to make one.

N02

Doors swing out

The default is outward, and it is a safety rule rather than a preference: a door that opens inward onto a hot bench is a trap.

N03

Rough openings add themselves

The framed opening is wider and taller than the door itself. The planner adds that margin so the crew is not doing it on site.

N04

Elevations at one scale

Every wall is drawn at the same scale on purpose, so two walls can be compared by eye without reading the numbers.

SHT-02ATiming diagram · inbound call

You know who it is before you answer.

The card arrives in Telegram before the phone rings. Not moments after, not on a good day: the system waits for the card to be delivered before it tells the network to start ringing, so the order is guaranteed rather than hoped for.

Recording stays off unless it is deliberately switched on, and when it is, both sides hear that it is happening before the call connects. A call can never move a project by itself; there is a test whose only job is to fail if that ever becomes possible.

Ref · sauna-crm · app/api/twilio/voice/route.ts

  1. Call reaches the system

    Twilio posts the webhook

  2. Who is calling is looked up

    Matched against the client list

  3. Card lands in Telegram

    Name, number, last project

  4. The phone starts ringing

    Only now is the dial returned

  5. Card before ring, by construction, not by luck

SHT-03Detail callouts

What the system does while nobody is watching.

These are the parts that pay for themselves: the work that used to depend on someone remembering.

A

The caller card arrives before the phone rings

An unknown number calls. Before the phone starts ringing, a card lands in Telegram with who it is, their number in a tappable format, and the last project they discussed. By the time it is picked up, the context is already there.

  • Recording is off by default
  • Both parties are told before any recording starts
  • Call history writes itself into the client card
  • A call can never move a project on its own

Ref · sauna-crm · app/api/twilio/voice/route.ts

B

Estimates and contracts sign themselves off

The client opens a link, reads the estimate, and signs on their phone. The signature page is composited into the real PDF along with both names, the date, and the audit trail. Branding comes from the company record, so the document looks like the contractor's, not like ours.

  • Built in-house, no DocuSign
  • IP and user agent recorded
  • Links expire; requests can be voided
  • Estimate, contract, certificate of completion

Ref · sauna-crm · supabase/migrations/028_esignature.sql

C

Deposits by card and by bank transfer

Built on Stripe Connect so the money lands in the contractor's own account. The platform fee comes out of their payout rather than being added to the homeowner's total, and bank transfer is offered because on a large deposit the card fee is the difference between five dollars and a few hundred.

  • Their Stripe account, their money
  • Card and ACH
  • Fees computed in whole cents
  • Refunds return the fee proportionally

Ref · sauna-crm · lib/deposit-fee.ts

D

A bot that only takes buttons

The crew moves a project forward by tapping a button in Telegram: deposit received, scheduled, work started, finished, paid. It refuses free text and voice notes on purpose, because one misheard word would move a real project.

  • Only the legal next step is offered
  • Installers can move work, not documents
  • Roles mirror the database rules
  • No free text, no voice

Ref · sauna-crm · lib/telegram-actions.ts

E

Quiet estimates get chased, but not by a robot

When an estimate goes quiet, the system drafts the nudge and shows it. Nothing is sent until it is read and sent deliberately. It waits business days, stops after three attempts, and is not allowed to invent a number that is not already on the estimate.

  • Drafts, then a second deliberate tap
  • Waits 3, 7, then 14 business days
  • Stops at three; weekends skipped
  • Writes in the owner's voice, not a marketer's

Ref · sauna-crm · lib/followup-draft.ts

F

The rest of the working day

Reports that agree with each other because revenue is defined in one module. Crew payouts under one rule instead of three that disagreed. Stock that comes off the shelf when a client approves, once, even if the webhook fires twice. A calendar where an installer sees their own jobs.

  • Reports, revenue, profit
  • Crew payouts
  • Inventory and stock deduction
  • Calendar and client portal

Ref · sauna-crm · lib/revenue.ts, lib/installer-pay.ts

SHT-03AConnections · moving in

Start by connecting what you already have.

Many businesses are not ready for a system of their own. What hurts is that the tools do not talk: QuickBooks, Google Sheets, Gmail, the phone, Stripe, the website form, WhatsApp, and a person retyping between them. Connecting them is a sprint, and when the business is ready for a CRM it is built onto the same routes rather than replacing them.

G

Connected to the tools you already run

Enquiries from the site form, the inbox and the messengers land in one record instead of three places. Money and paperwork stop being retyped between tools: QuickBooks connects on the owner's own account through its own sign-in, and the module that writes the customer, the invoice and the payment into it is part of the same code. Each connection is one route, scoped in writing, on your keys.

  • Site form, email and messengers into one record
  • QuickBooks on your own account, through its sign-in
  • Stripe deposits into your own account
  • One route per sprint, on your API keys

Ref · wallflow-saas · lib/quickbooks.ts, lib/followups/gmail.ts; sauna-crm · lib/deposit-fee.ts

Read the guide →
H

Outside data that arrives by itself

Some of what a business runs on lives on other people's sites. A supplier's whole catalogue, with its prices, descriptions and photographs, came into the sauna builder's shop by a script rather than by hand, and an industry directory of installers came into WallFlow's lead list deduplicated by phone and email. Reviews and leads from marketplaces would come in the same way: a source, a schedule, and a rule for duplicates, written down first.

  • Supplier catalogues and prices imported, not retyped
  • Outside lead lists deduplicated by phone and email
  • Source, schedule and duplicate rule in writing
  • Your keys and your accounts

Ref · sauna-crm · scripts/import-bathingbrands-catalog.mjs; wallflow-saas · scripts/import-wia-leads.mjs

I

Your history comes with you

Years of clients, quotes, notes and files in HubSpot, Pipedrive or a spreadsheet are not lost. They are moved before the switch and counted on both sides. The importer written for the sauna builder's spreadsheet of past estimates shows the rules: his own estimate numbers are kept, no won or lost status is invented, and every row arrives archived as history until he decides otherwise. The old system stays readable until you decide to turn it off. Migration is its own line in the scope, priced by the state of the data rather than the row count.

  • Clients, deals, history and files
  • Counted on both sides before the switch
  • No won or lost status invented
  • Its own line in the scope, never absorbed

Ref · sauna-crm · scripts/import-estimate-history.mjs

J

A weekly report that sends itself

Once a week the owner gets one short message: how many enquiries came in, how many are still unanswered, how much money sits in open quotes, and where work is stuck. No spreadsheet assembled on a Sunday night. The parts exist in WallFlow: a weekly money summary to the owner's Telegram and a digest of replies nobody has answered yet. One message a week, on purpose: when that system sent six to eight a day, its own owner switched the notifications off.

  • Enquiries in, enquiries unanswered
  • Money in open quotes
  • Where work is stuck
  • One message a week, from the same records

Ref · wallflow-saas · app/api/cron/weekly-revenue/route.ts, lib/followups/digest.ts

Read the guide →

SHT-04QA schedule

Why we trust it, and how you can check.

Custom software is only worth it if it is right. These are the habits that keep it right, and each of them exists because something once went wrong without making a sound.

Tests in this system
3152
Run: 249 files, 3152 passing
Database migrations
189
Every schema change, in order
Guard tests
30
They read the source, not the output
N01

One module owns each fact

How a price is printed lives in one file. What revenue means lives in one file. When the same fact is computed twice, the two eventually disagree, and they disagree on a screen a client is looking at.

N02

A rule that lives in two languages changes in both

When a formula exists in the application and in the database, both move in the same commit or neither does. Otherwise the database quietly computes one number while the screen shows another.

N03

A guard test earns its place by failing first

Each one was run against the code that shipped the bug it guards against, and had to flag exactly that code before it was allowed in. A test that has never failed is decoration.

N04

Empty and broken must never look the same

A query that errors and a query that finds nothing both return something falsy. Checks here are written so a mistake reports as a mistake instead of printing a confident zero.

SHT-05Comparative notes

When custom is the wrong answer.

Rented software is the right call more often than a studio will admit. Here is the honest line between them.

Use HubSpot or Salesforce when

  • Your process looks like everyone else's in your industry
  • You need it running next week, not next month
  • Nobody will own the system after it launches
  • Per-seat pricing is cheaper than a build at your team size

Build custom when

  • The thing that makes you money is the thing no product models: your pricing math, your documents, your trade
  • You are paying people to retype the same numbers between tools
  • The workaround has become the process
  • You want to own the code and the database outright

SHT-07General notes

Asked before every system.

N01What happens to the data in my old CRM?
It comes with you. Clients, deals, notes, history and files move from the old CRM or the spreadsheets before the switch, and the counts are checked on both sides before anything is turned off. Migration is its own line in the scope, priced by the state of the data rather than the number of rows.
N02Can we start with integrations and grow into a CRM later?
Yes, and that is the usual order. Connecting the tools you already have is a sprint. A later system is built onto the same routes, so what you paid for is kept rather than replaced.
N03Is there AI in every system?
No. The sauna builder's system has none, on purpose. AI goes where it closes a gap in the work, not where it looks good in a deck.
N04Who owns the code and the database?
You do, from the first commit: your repository, your database, your Stripe account. If we stop working together, nothing switches off.

SHT-10Issued for construction

Start with the audit.

A short video walkthrough of where your lead flow leaks and what it would take to close it. Free, and there is no call afterwards unless you want one.

  • Where the lead flow leaks, walked the way a buyer walks it
  • The one fix worth doing first, and what it is costing to leave
  • What to automate first, and what is not worth automating yet
Get my free audit →

30 seconds to submit · delivered same day · no card, no call