Most founders ask me "should we build custom or buy no-code?" That framing is wrong. The real choice is which point on a spectrum fits this workflow this quarter, not which camp you swear loyalty to.
I ship ops for SMBs that live in CRMs, WhatsApp threads, and missed-call queues. My default stack for that work is n8n orchestrating GoHighLevel (or HubSpot), with Make for light glue and custom code only when the hard 10% earns it. Here is how I decide.
The short answer
| Need | Prefer | Why |
|---|---|---|
| Simple SaaS glue, low volume, no engineer | Make | Fastest path to a working scenario |
| Lead chase, CRM ops, high volume, AI steps | n8n + GoHighLevel | Cheap at scale, Code node, owns the CRM loop |
| Product feature, weird APIs, hard latency | Custom service | Tests, CI, and full control |
If your workflow is "form to sheet to Slack," start on Make. If it is "missed call to qualify to book to pipeline," I reach for n8n wired into GoHighLevel almost every time.
What Make actually wins at
Make (the old Integromat product) is a hosted visual builder. You drag modules, wire a scenario, and Make runs it on their cloud. No server to patch. That is a real advantage for a two-person ops team.
It shines when:
- The flow is linear or has a few simple branches
- You need a connector that already exists and you will not fight the payload shape
- Volume stays in the hundreds of runs per month, not tens of thousands
- Nobody on the team wants to own Docker, queues, or deploys
I have watched non-engineers ship a working Make scenario in an afternoon. Reaching for custom TypeScript there is ego, not engineering.
Docs worth bookmarking: Make modules help and the Make pricing page so you can model ops cost before you fall in love with the canvas.
Where Make (and pure no-code) starts to hurt
The ceiling shows up in predictable ways.
Nested branches, dedupe against prior runs, and "remember what happened three steps ago" turn a clean canvas into a spaghetti map only the author can debug. Reading a CRM is forgiving. Creating an invoice, updating stock, or booking a calendar slot is not, because shallow connectors hide the exact payload until something corrupts a live record.
Make bills by operations. A pilot that feels cheap can become a painful line item once lead volume spikes. You cannot cleverly optimize out of product pricing. And production automation is mostly retries, backoff, and "what if the CRM timed out halfway." Basic error routes exist. Transactional behavior gets awkward fast.
That is usually when I move the same job onto n8n instead of forcing more modules onto the canvas.

Why n8n plus GoHighLevel is my SMB default
n8n is a node-based workflow tool with a visual editor, like Make, plus two properties that change the math: you can self-host it, and you can drop a Code node and write JavaScript or Python without leaving the flow.
For customer-facing ops I usually pair it with GoHighLevel as the system of record: contacts, pipelines, calendars, SMS and email, and often the AI or voice layer that fronts the business. n8n becomes the orchestration brain. GHL holds the customer state.
Self-hosted n8n runs on a box you already pay for, so ten thousand extra executions do not auto-inflate a SaaS ops bill the way per-operation platforms do. Complex scoring, dedupe, or payload shaping lives in a few lines of code instead of twenty visual branches. Lead qualify, missed-call follow-up, appointment booking, and pipeline stage moves are the daily bread of GHL shops, and n8n webhooks plus HTTP nodes plug into those events cleanly. Workflows are also JSON, so you can export them, put them in Git, and review diffs like any other artifact. See the n8n docs for hosting and webhook patterns.
I am not anti-Make. I am anti-paying Make prices for a high-volume lead loop that belongs on a self-hosted orchestrator sitting next to the CRM.
A tiny pattern I use constantly: GHL (or a dialer) hits an n8n webhook, the workflow enriches and routes, then writes back to the CRM.
const body = $input.first().json.body ?? $input.first().json; const contactId = body.contactId; const source = body.source ?? "unknown"; const phone = String(body.phone ?? "").replace(/\D/g, ""); if (!contactId || phone.length < 10) { return [{ json: { ok: false, reason: "missing_fields" } }]; } return [ { json: { ok: true, contactId, source, phoneE164: `+${phone}`, tag: source === "missed_call" ? "needs_callback" : "new_lead", }, }, ];
Next nodes update the contact, enqueue a follow-up, or call an AI agent. The visual canvas stays readable. The fiddly bit stays in code.
When custom code is the honest answer
Custom services (TypeScript, Python, whatever your team ships) win in a narrower set of cases than Twitter threads suggest.
Reach for custom when:
- The logic is genuinely bespoke (scoring models, heavy transforms, multi-step domain rules)
- Latency or throughput needs are product-grade, not "batch within a few seconds"
- The automation is the product feature, so it deserves tests, CI, and observability like the rest of the app
- The integration is weird: legacy SOAP, private protocols, or APIs no connector covers well
The trap is building a microservice for a booking sync because it feels more "serious." You then own hosting, retries, monitoring, and a codebase only one developer understands, to solve a problem n8n already handles.
Custom code should earn its seat. Prestige is not a requirement.

The hybrid I ship most often
Serious ops stacks are rarely pure. The pattern that ages best: Make for internal, low-volume glue nobody wants to maintain as infrastructure; n8n for orchestration (schedules, webhooks, retries, SaaS connectors, AI HTTP calls); GoHighLevel or HubSpot as the CRM and messaging surface operators live in; and a small custom service only for the hard slice, called over HTTP from n8n.
When a no-code step outgrows itself, you replace that one node with code. You do not rewrite the whole system.
Official starting points if you want to poke the tools yourself:
Four questions before you pick a stack
Answer these in order for one workflow, not for "automation in general."
- How complex is the logic? Linear and simple points to Make. Stateful, conditional, or transactional points to n8n (Code node) or custom.
- What volume will it hit? Hundreds of runs a month can live on per-op pricing. Tens of thousands argue for self-hosted n8n.
- Where does customer state live? If operators already live in GoHighLevel, wire n8n around that CRM instead of inventing a second source of truth.
- Who maintains it in 12 months? No engineer on staff favors managed Make for simple jobs. An engineer who will own it can take n8n or custom and get more control for the responsibility.
Most businesses end up mixed. That is fine. Trivial spreadsheet sync on Make. Customer-facing lead chase on n8n + GHL. One latency-sensitive scoring job as a tiny service. Choosing per workflow beats picking a religion.
What I would do this week
Pick one painful workflow. Write down the trigger, the systems it writes to, expected monthly volume, and who will debug it at 11pm. Place it on the table above. Build the thinnest version that clears the bar. Measure cost and failure modes for two weeks before you "upgrade" the stack.
If you want a second pair of eyes on that workflow (Make vs n8n + GHL vs a custom slice), book a free discovery call. Bring the messy version. Those are the ones worth fixing.

