Build in Public
A07

Chapter 7: Building Zo.E

7 min read · 1,572 words ·

The first thing I want to clarify is the name.

Zo.E stands for Zero-Overhead Engine. The goal when I started building it wasn't to make something impressive — it was to make the overhead of running a content operation as close to zero as possible. That constraint shaped every decision in the architecture. If something required me to actively do it, it was a problem to be solved. If it could run without me, it did.

That's it. That's the design philosophy. Everything else follows from there.


The constraint came first, not the system

I have 4 to 7 hours a week for all of this. Day job, real clients, real deals — that's where most of my energy goes, and I'm not apologetic about it.

When I started thinking about what a content operation for two brands would look like — Sales Source Code targeting sales professionals, Nunlimited targeting entrepreneurs — the honest maths didn't work. Two platforms, multiple posts per week, engagement, analytics, the article-sourcing and inspiration layer on top. If I was doing that manually, I wasn't doing the day job properly. If I was doing the day job properly, the content operation would die in week three.

So before I thought about what to build, I decided what I wasn't willing to do. I wasn't willing to write first drafts from a blank page every morning. I wasn't willing to manually schedule posts. I wasn't willing to log into a dashboard to check how things were performing. I wasn't willing to spend a Sunday doing a content audit.

The system had to handle all of that. My job was a single decision per post: approve or reject.

That one constraint — one decision per post, everything else automated — is what Zo.E is built around.


Ten workflows, one pipeline

The way I think about Zo.E is as a pipeline with stages. Each workflow does one job and hands off to the next. Described sequentially:

WF1 runs twice a day and scans RSS feeds — tech, business, sales, entrepreneurship. Gemini scores each article for relevance. The ones that score well get queued in NocoDB as inspiration sources.

WF2 runs at 6am. It picks up a brief or content pillar, pulls relevant articles from the queue if there are any, and generates a post and image via Gemini. The image gets uploaded to Cloudinary; the URL goes into the post record.

WF3 is the notification layer. It takes the generated post and sends it to me via Telegram — formatted cleanly, with Approve, Reject, and Edit buttons underneath.

WF4 handles whatever I press. Approve marks the post in NocoDB. Reject sends it back for regeneration. This workflow is the one that taught me the importance of activation order — WF4 has to be live before WF5, or approved posts pile up with nowhere to go. I discovered this in production, not testing.

WF5 runs at 7am. It picks up everything with an Approved status and queues it for publishing via the Upload Post API — which handles LinkedIn, Instagram, Bluesky, X, Threads, all from a single API call.

WF6 runs every four hours and tracks post performance. When engagement spikes, it flags it.

WF7 runs twice daily, scanning Reddit, Hacker News, and Bluesky for comment opportunities — discussions where it makes sense to contribute.

WF8 is a webhook. When WF7 surfaces an opportunity, I get a Telegram notification with options: flag it, skip it, or reply. If I reply, WF8 drafts and sends the response.

WF9 runs on Sunday evenings and produces a full weekly analytics digest — Gemini-generated insights included.

WF10 is ad-hoc. I send an idea to a webhook; platform-specific posts come back within seconds.

Ten workflows. The whole pipeline runs largely without me. My involvement is the Approve/Reject step — and the ad-hoc ideas I feed WF10 when something's on my mind.


Why those tools and not other tools

This is usually where people expect the impressive answer. It's not particularly impressive.

n8n because it's self-hosted. I run it on PikaPods for roughly £3-5 a month. The reason I didn't use Make or Zapier isn't ideology — it's economics. Per-operation pricing at the volume I needed made no sense. Self-hosted also meant I could write proper code nodes, loops, and conditional branches. The workflow logic here isn't linear; it has forks, loops, and runtime decisions that needed something developer-grade.

NocoDB because it's free at this use case's volume, SQL-compatible, and API-first. Six tables: Posts, Engagement Log, Analytics Snapshots, Article Sources, Article Queue, Engagement Opportunities. It looks like a spreadsheet. It behaves like a database. That's exactly what I needed.

Gemini because the free tier handles my generation volume. Simple as that. I use it for both content generation and scoring. The prompts took considerably longer to get right than the infrastructure did — more on that shortly.

Telegram for approvals because it's free, async, mobile-first, and requires no login. This one deserves a bit more explanation because it's the actual insight in the system.

The previous version of my approval process involved checking a dashboard. The problem with a dashboard is that it requires context-switching. You finish a meeting, you think "I should check the content queue", you open a browser, log in, navigate to the right view — and by that point something else has come up and you haven't done it. Dashboards require intention. They sit there waiting for you to remember them.

Telegram is the opposite. The notification comes to me. I see the post in the notification itself. I tap Approve or Reject without opening anything, without logging in, without having a screen in front of me. I can do it standing on a platform or between calls. The friction is essentially zero. That asymmetry — a push model instead of a pull model — is what makes the approval loop actually work in practice.

The early version of this sent too much text and made the post hard to read on mobile. A few iterations later, it sends the post cleanly formatted, with a short context line above it and the buttons below. One tap. Done.

Cloudinary because the free tier handles image hosting and the URL format works cleanly in the publishing layer. Not exciting. Works.

Upload Post for the publishing layer because publishing to seven platforms from one API call beats maintaining seven separate integrations. That's the whole reason.


What took the most time

Not the plumbing. The prompts.

Getting Gemini to write in a specific voice, at consistent quality, across two distinct brand audiences, over many posts, without drifting into generic content — that took many rounds of iteration. The infrastructure was, relatively speaking, the straightforward part. The technical setup has well-documented patterns. The prompts don't.

The challenge with voice consistency is that an LLM will produce something that looks right but isn't quite right, and the gap is hard to articulate. You know the post doesn't sound like the brand. You struggle to write in a system prompt exactly what it is that makes it wrong. So you iterate, test, adjust, iterate again. That work is less visible than building workflows but it's where most of the time went.


The dual-brand mistake

I built the first version of Zo.E for Sales Source Code only.

Adding Nunlimited later required: a second Telegram bot, a second NocoDB base with a mirrored table structure, and code nodes in WF6 and WF9 that detect which brand's data they're processing at runtime and switch their tableId accordingly. The shared workflows now route based on a brand flag in the execution data.

It works. But it was harder than it needed to be, and the architecture shows its origins. If I'd built dual-brand from day one, the code nodes would look different — cleaner, more deliberate. Instead they read like what they are: a retrofit.

The lesson is one I should have already known from sales. Scope creep doesn't make things bigger; it makes them messier. If you know you'll need to support multiple things, build for multiple things from the start, even if you're only using one thing initially. The extra effort upfront is smaller than the refactoring cost later.


When it felt like a product

There was a specific moment. I was in a meeting — a real work meeting, proper day job, talking through a deal — and I got a Telegram notification. Gemini had generated a post for SSC, sent it to me for approval, and was waiting. I read it in about twenty seconds. It was good. I tapped Approve and put my phone down.

The meeting continued. I hadn't broken stride. The post was approved, queued, and scheduled to go out without me touching anything else.

That's when it shifted from "a system I built" to "something someone else could use." The value isn't the ten workflows. The value is the twenty seconds. The value is that the content operation didn't require me to stop what I was doing, context-switch, open a dashboard, think about it, come back to it.

That's the zero-overhead idea made concrete. And that's when I knew it was worth packaging.


Chapter 8 is where it stands now — the numbers, what's working, what isn't, and what comes next.

Ta,

James
Founder | Nunlimited

James Nunn signature