· 4 min read
Why I stopped building automations
I spent years on no-code and low-code automation work, and it taught me how to design systems. In the end the tools couldn't hold what I'd learned.
What I actually spent years doing
I spent a long stretch of my working life building automation systems on no-code and low-code platforms, the kind of tools where you wire up triggers and actions in a browser instead of writing a program. Four of the projects on this site came out of that work. There's a review platform that replaced spreadsheet sprawl, a social media scheduling system, a lead enrichment pipeline and a lead import system. They solved real operational problems for the people who needed them solved, and I'd stand behind every one of those builds today.
But I kept hitting the same ceiling on different projects, until it stopped looking like bad luck and started looking like a limit of the tools themselves.
The ceiling, concretely
There's no real type system. A field gets renamed somewhere upstream and nothing catches it. The automation that depended on the old name just stops working, and the first anyone hears about it is someone asking the next day why nothing happened.
Tests don't exist either, in the sense of something you can run before you trust a change. You make the change and then you watch, so every deployment is also the first test. Version control doesn't exist in any meaningful sense, so there's no diff between what it did yesterday and what it does today, no revert, and no blame to tell you what changed and when. If something goes wrong, you have to rebuild the previous behaviour from memory, because you can't roll back to it.
You don't get a local environment. Changes are made directly in a browser, against the live production configuration, because there usually isn't another one to make them in.
And you can't really reason about failure modes ahead of time. When something breaks, you open an execution log and read through what happened, and mostly you're guessing at why, because the platform gives you an audit trail of outcomes instead of a model of the logic that produced them.
Reuse, in this world, means copying an existing scenario and editing the copy. There's no function to extract, no shared module, and no single place to fix a bug once so every caller benefits. Every copy is a fork that you'll have to find and fix again next time.
When it starts to matter
None of this shows up as a problem when things are small. A single automation with a handful of steps, run a few times a day, doesn't need a type system or tests or a diff history, because a person can hold the whole thing in their head and check it by reading it. That's most of what these platforms get used for, and for that I think they really are the right tool. For the right problem at the right scale, a no-code platform is the correct engineering answer, and I don't see it as a compromise on the way to something better.
It changes once the thing becomes load-bearing. When a business depends on an automation to run correctly every day, unattended, missing all of those things above stops being a minor inconvenience and becomes the main risk. You can't change it with any confidence because you can't test the change, and you can't really debug it because all you can inspect is its output, never its logic. You also can't safely hand it to someone else, because nothing represents it apart from the live configuration itself.
What actually transferred
Looking back, I think the platform was the least important part. What I was really doing, project after project, was modelling a domain. I was working out what the real entities were, what depended on what, and what could change independently of everything else. I was designing for failure, deciding what should happen when a step didn't run. And I was deciding which system was the single source of truth for each piece of information, so two systems never ended up disagreeing about it.
That's systems design, and it's what the four projects on this site were really about too (data modelling, workflow logic, failure handling, ownership of state), whatever platform they happen to be built on. It can be expressed in a drag-and-drop tool, and for a long time I expressed it that way because the tool was quick to reach for and the results shipped fast.
The platform was a good place to learn it, because the feedback loop is fast and it's easy to just try something. It turned out to be a poor place to keep practising it, though, because the tool hides the software design and shows off the automation. Past a certain point, the things it's missing (the types, the tests, the version control, the local environment) get in the way of the design you're trying to hold in your head. Moving off it didn't change what I was doing. It just stopped the actual work from hiding behind the part that was easy to demo.
It would've been the same thinking in any language, on any stack.