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
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-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.

0
Tests in this system

Run: 49 files, 557 passing

0
Database migrations

Every schema change, in order

0
Guard tests

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-08Issued 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.

Get my free audit