Turning 60–90 minutes of manual client setup into a 2–4 minute automated pipeline.
When a local service business signs with More Than Momentum, someone historically had to manually create a GoHighLevel sub-account, install the CRM snapshot, find form and calendar IDs, build embed codes, generate a custom website, inject those widgets, and deploy everything. That's 90 minutes of repetitive work — per client, every time.
This system does all of it in under 4 minutes, triggered by a single row in a Google Sheet.
A new client signs → a row in the Build Tracking spreadsheet is marked YES → the pipeline runs automatically:
- GHL sub-account created — correct CRM snapshot installed, pipeline and stages configured
- Website generated — Claude AI writes a fully custom multi-page website tailored to the business (name, industry, services, branding colors)
- GHL widgets embedded — contact forms and booking calendars are injected directly into the generated HTML
- Site deployed to Cloudflare Pages — live URL, no manual intervention
- Spreadsheet updated — result written back to the row automatically
One manual step remains: creating a location-level Personal Integration Token inside the new GHL sub-account. This is a GoHighLevel permission constraint — their API doesn't allow agency-level tokens to read sub-account resources. Everything else is automated.
Small service businesses (HVAC, landscaping, cleaning, contractors) getting their first real digital marketing presence. Many of these clients have never had a functional website or a CRM — they're running their business from a notes app and a Facebook page. This pipeline gets them from "just signed" to "fully operational" without waiting days for manual setup.
Three Cloudflare Workers, coordinated by an orchestrator:
Google Sheets (trigger)
│
▼
Apps Script (Apps Script polls and calls orchestrator)
│
▼
┌─────────────────────────────────────────────────────┐
│ orchestrator-worker │
│ Sequences the pipeline, streams progress via NDJSON │
│ Persists results to KV, handles two-phase logic │
└──────────┬────────────────────────┬─────────────────┘
│ Service Binding │ Service Binding
▼ ▼
┌──────────────────┐ ┌──────────────────────────┐
│ website-generator│ │ ghl-configurator │
│ │ │ │
│ Calls Claude API │ │ GoHighLevel REST API │
│ Streams HTML via │ │ Creates sub-account │
│ SSE → NDJSON │ │ Polls for snapshot assets │
│ Deploys to Pages │ │ Returns embed codes │
└──────────────────┘ └──────────────────────────┘
Cloudflare Service Bindings — inter-worker calls never leave Cloudflare's network. Zero latency, no HTTP overhead.
Cloudflare Queues — the orchestrator enqueues jobs rather than processing inline. Workers have a 30-second HTTP timeout, but Queue consumers get 15 minutes per batch — enough for a full pipeline run including Claude generation and GHL polling.
Streaming NDJSON with heartbeats — Claude's website generation takes 60–120 seconds. A TransformStream emits heartbeat events every 5 seconds to keep the connection alive through every layer (Claude → website-generator → orchestrator → Apps Script), alongside real progress events (character counts as the site is written).
Two-phase KV handoff — GoHighLevel's API won't let agency-level tokens read sub-account forms/calendars. Phase 1 creates the sub-account and generates the site, caching everything to KV with a 24-hour TTL. A human creates a location-level token inside GHL. Phase 2 restores state from KV, runs the final setup with the location token, injects embed codes, and deploys.
| Layer | Technology |
|---|---|
| Compute | Cloudflare Workers (3 workers) |
| Job queue | Cloudflare Queues |
| State / cache | Cloudflare KV |
| AI | Anthropic Claude claude-sonnet-4-6 (streaming) |
| CRM | GoHighLevel REST API |
| Deployment | Cloudflare Pages Direct Upload API |
| Trigger | Google Apps Script (Sheets-bound) |
| Language | JavaScript (ES modules) |
Service Bindings over HTTP — inter-worker calls via HTTP would add latency, require auth, and create another failure surface. Service Bindings are instantaneous and internal.
Queues over ctx.waitUntil() — waitUntil is hard-capped at 30 seconds on all Workers plans. Queue consumers get 15 minutes. For a pipeline that includes streaming a full website from Claude and polling GHL for snapshot artifacts, 30 seconds isn't close to enough.
max_retries = 0 on the queue — intentional. Retrying a failed pipeline job would silently create a duplicate GHL sub-account. The catch block writes failure state to KV, which the Apps Script poller reads and writes back to the spreadsheet.
mtm-system/
├── colby-side/
│ ├── orchestrator-worker/ # Pipeline coordinator (NDJSON streaming, Queues, KV)
│ ├── website-generator-worker/ # Claude AI site generation + Cloudflare Pages deploy
│ └── ghl-configurator-worker/ # GoHighLevel sub-account creation and setup
├── cole-side/
│ ├── website-generator-tool/ # Admin tool for demo site generation (pre-sale)
│ ├── edit-pass-worker/ # Refines demo HTML post-sign
│ ├── demo-router-worker/ # Resolves *.preview.morethanmomentum.com
│ └── analyzer-endpoint-worker/ # Exposes analyzer JSON for the pipeline
├── docs/
│ ├── HANDOFF.md
│ └── architecture-decisions/
└── README.md
Each worker deploys independently:
cd colby-side/orchestrator-worker
npm install
wrangler deploySecrets are managed via wrangler secret put — never committed to the repo. See each worker's wrangler.toml comments for the full secrets list.
Required secrets per worker:
orchestrator-worker: AUTH_SECRET
website-generator-worker: ANTHROPIC_API_KEY, CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, AUTH_SECRET
ghl-configurator-worker: GHL_API_KEY, GHL_COMPANY_ID, AUTH_SECRET
Built for More Than Momentum, a digital marketing agency serving local service businesses. The automation runs every time a new client onboards. It has reduced per-client setup time by ~95% and eliminated the category of errors that came from copy-pasting embed codes manually into HTML.