03 · Selected Work

AI Marketing OS

A multi-tenant AI marketing operating system for high-value local businesses. Five subsystems run as one closed loop: what the market says shapes strategy, strategy becomes campaigns, campaigns produce leads, leads produce revenue — and the revenue is measured and fed back into the next strategy.

Role

Engineer

Year

2026

Type

Multi-tenant product

Stack

Next.js · TypeScript · Postgres · Claude · n8n

The problem

Marketing tooling produces artefacts. A campaign lives in one product, the leads land in another, the spend is reconciled in a spreadsheet, and the report at the end of the quarter is assembled by hand. Each piece works. Nothing connects what the market said to what was spent to what came back, so the next decision starts from opinion rather than from the last result.

This closes that loop in one system, and makes every step of it inspectable.

The loop

The feedback edge is the product. Without it these are five tools; with it they are one system.

The product

The dashboard: revenue against spend, a spend-to-revenue funnel, and pending AI recommendations.

One organisation’s loop in a single view — spend, leads, customers and revenue, with every figure carrying its own provenance. Demo data, labelled MOCK throughout.

The approvals queue, showing a proposed budget reallocation with its reasoning and Approve, Edit payload and Reject controls.

The gate. An agent proposed moving budget between campaigns; nothing has happened. The reasoning, the expected effect and the exact payload are all inspectable before anyone approves it.

The leads table, each lead carrying a numeric score, a grade and its source.

Every lead carries a score you can open up. The factors behind it have to sum to it, or the tool rejects the score.

Decisions worth noting

Tenancy is enforced three times over. Session, then a membership and role check, then PostgreSQL row level security. The app connects as a non-owner role, so a query that forgets its organisation filter returns nothing rather than another tenant’s data. A test attacks that boundary directly rather than trusting it.

Agents read through a tool layer, not a prompt. Every tool opens its own tenant-scoped transaction, so an agent cannot read across organisations even if a prompt instructs it to. Context is fetched, never stuffed.

No agent can publish, spend, or contact anyone. Those actions become approval rows instead. A reviewer can edit the payload before approving, and the edited version is what executes — so review is a real decision, not a rubber stamp.

Lead scores have to explain themselves. Every score carries the factors that sum to it, each with its own evidence, and the tool rejects a score whose factors don’t add up. A number nobody can interrogate is worse than no number.

Nothing pretends to be real. With no configuration the app runs on an embedded Postgres and a deterministic rule engine in place of Claude, and everything produced that way is labelled MOCK in the interface. A demo is never mistaken for client data.

What is deliberately not built

Google Ads and Meta Ads have interfaces and working mocks, but no live API clients. The Integrations screen says so per provider rather than offering a connect button that does nothing. Email and SMS delivery are the same.

Real spend is blocked behind a flag that defaults to off, so wiring up a provider cannot cause spend by accident. Knowing what to leave unfinished — and saying so in the interface — is part of the work.

Interested in something similar?