Decision paper · Tuesday 18 August 2026

We are already leaving GoHighLevel. That is why we should not start a migration.

Ken asked for the pros and cons of building our own CRM, and for nothing to be built. This is that paper. The short answer is: don't start — but not for the reason you'd expect. Six pieces of this business have already left GoHighLevel since 5 August, one at a time, none of it ever called a migration. The risk isn't staying too long. It's stopping something that is working in order to do it all at once.

6pieces already moved off GHL
0workflows an API can ever build
?what we pay GHL — not recorded anywhere

The boundary Ken set first


Ken, 18 August

“The thing I don't want to do is get rid of GHL's email. I do want to replace their CRM, their other functions, but the email has to stay.”

That is a liability position, not a preference. A third-party processor absorbs the spam, unsubscribe and bounce exposure. Take it in-house and we own it.

⚠️ And the boundary is wider than the word “email”

Traced through the code on 12 August: every consumer-facing message goes through GoHighLevel — booking confirmations, reminders, review requests — and GHL owns the unsubscribe link, the unsubscribe status, and bounce suppression. Our own Telnyx sender only ever texts the inspector or our own staff, never a homeowner.

So the real constraint is: GHL keeps everything that touches a consumer's inbox or handset. Anyone scoping this later must read it that way, not as “keep the templates”.

The part nobody expects

We have been leaving GoHighLevel for months, one piece at a time, without ever calling it a migration.


WhatWhere it lives nowSince
Availability / the diaryOur own code, from the onboarding record5 Aug
Double-booking gateapi/ghl/slot-claim.js — ours12 Aug
Client setup (Brain 2)onboarding.json, data branchongoing
Call transcriptsTelnyx → our endpoint27 Jul
Call recordingsTelnyx, played in the portal13 Aug
Booking editsThe Inspector Portal5 Aug

api/ghl/availability.js opens with Ken's own reason: “We need our own calendar managing our operating system inside — I don't want to peek at the ISN.”

This changes the question. It is not “should we build a CRM”. It is “should we stop finishing something we are two-thirds through, in order to do the rest all at once” — a smaller and much better-understood question.

What still depends on GoHighLevel


FunctionModuleReplacing it
Contacts — the client recordcontact-record.jsMedium. It is a table.
Work order · transcript · reportcontact-record.jsEasy — notes on a contact
Appointments → inspector's phonecalendar.js⚠️ Hard. See below.
Consumer email & SMSGHL🔴 Not replacing — Ken's ruling
Sub-account per clientagency.jsOnly needed while GHL is the CRM
Workflows / automationsGHL UI⚠️ No API. Ever.
Payments / invoicesGHL + StripeNever run end to end — no client has connected a Stripe
⚠️ The appointment sync is the underrated one

api/ghl/calendar.js turns a booking into a real appointment on the inspector's GHL calendar, which two-way-syncs to their Google Calendar and therefore their phone.

That is not a database row. It is the thing that makes an inspector trust us — Chad looks at his phone and the job is there. Replacing it means a two-way calendar integration per inspector, or asking inspectors to check a web page instead. “Check our portal instead of your phone” is how you lose a client who was otherwise happy. Nothing else on this list is close to it in risk.

The case for building it


01

The workflows can never be automated, and that is permanent

GoHighLevel has no workflow API — confirmed in the 13 July MCP audit and again on 12 August. Every automation for every client is hand-built in the GHL screen, by a person, forever. No agent can ever do it. GHL_CLIENT_SNAPSHOT_ID is unset and all six accelerant snapshot ids are empty, so today each new client's CRM is built bare, by hand.

This is the strongest argument here, because it does not improve with scale — it gets linearly worse. At 13 testers it is 13 hand-builds.

strongest argument
02

We already carry the data model

The onboarding record is the source of truth for prices, services, schedule and inspectors. GHL holds a copy of the outcome, not the definition.

03

Custom fields are a known failure surface

GHL accepts an unknown custom field id silently and drops the value — that is what made Ted's first six bookings arrive hollow on 29 July. We refuse rather than post now, but the trap is theirs.

04

The Files API is closed to us

An inspection report is filed as a link, not an upload, because GHL's Files API needs media scopes we do not hold. Audio can never be a file in that CRM. Ken's USP — work order, transcript and report on one record — is being delivered around a limitation rather than through a feature.

The case against


01

It is not the bottleneck

Nothing on the launch list is blocked by GHL. Chad and Ted are blocked on an onboarding form, a phone that sounds right, and four Hamming assertions. A migration would consume the exact weeks those need.

02

The compliance surface is the real product risk

Unsubscribe handling, bounce suppression, STOP processing, and the TCPA exposure on customer-uploaded lists. A half-migration that accidentally drags a consumer message out of GHL is a legal problem, not a bug.

03

We would be building it for two clients

Both already have working sub-accounts. And the confidentiality worry that once justified urgency turned out not to be true — Quality and GC have had separate sub-accounts all along.

04

Some of it we cannot cost

Any savings figure produced today would be invented, and this business has been bitten twice by numbers that were read rather than measured.

What it costs — and what we cannot honestly say


From the record:

ItemFigureSource
Josh, per minute$0.064scripts/cost-per-minute.mjs, 16 Aug
Web agent, per session$0.14–$0.1911 Aug costing
Telnyx recording$0.002/min, storage $013 Aug
⚠️ Two numbers that do not exist, and must not be guessed

What we pay GoHighLevel. Not in the repo. It is on an invoice.

What a migration would cost in hours. The build board reads zero accounts — it filters status === "submitted" while Chad is draft and Ted is active. Neither came through the pipeline, so nothing about their setup was ever timed and it cannot be reconstructed.

That second gap is cheap to close: a minutes box on a support ticket, a started/finished stamp per build-board step, and a “who did it” picker. About an hour — and then it measures itself. Without it, this decision gets made on feel at the 13th tester too.

The recommendation


Do not build. Ken is right, and the reasoning holds up better than “bigger fish to fry” suggests. Three things are worth doing that are not a migration:

Worth doing now

  1. Build the GHL base snapshot once — days, not weeks. The highest-value item in this paper, and it is a GHL job rather than a migration. It turns every future client's hand-built CRM into a clone. It pays off whether we ever leave or not — and if we do leave, the snapshot is the specification of what to build.
  2. Close the timing gap — about an hour. Then the 13th-tester review has real numbers instead of another argument.
  3. Confirm the V1 token deprecation is handled — ten minutes. Flagged 13 July, unconfirmed since. If the integration is on V1 it stops working on HighLevel's schedule, not ours, and that forces this decision at the worst possible moment.

When to revisit


At the 13th onboarded tester, as Ken said — with the timing data in hand. Three things would move the answer sooner:

TriggerWhat it changes
GHL ships a workflow APIThe strongest argument for leaving evaporates
An inspector asks for something the CRM can't doThe Files API limit is the likely candidate
The V1 token cutoff lands unpreparedNot a decision any more — an outage

One thing to hold on to


The reason this paper recommends waiting is not that in-house is wrong. It is that we are already doing it, incrementally, and it is working — the diary, the slot gate, the transcripts, the recordings, the portal, the client setup. Each one left GoHighLevel when there was a concrete reason, and each one shipped in days rather than months.

The failure mode to avoid is not “staying on GHL too long”. It is stopping everything to do a migration we have been completing successfully by not calling it one.