Ian McPherson
Wide morning sky over open land on Ærø

Portfolio

Selected builds

Most of what I build is internal, so none of it has a landing page. These apps run a continuing-education business from the inside: integrations, data pipelines, launch tooling, a couple of AI systems. One of them runs my kids' homeschooling instead.

Internal · live

Next.js
Neon Postgres
pgvector
MCP integrations

ATW Intelligence

One place where the numbers agree.

The company's numbers lived in four systems that didn't agree with each other: the CRM, the ad-attribution platform, and the video and community tools. Answering something simple, like how a launch actually did, meant opening all four, exporting spreadsheets, and reconciling by hand for an afternoon. This pulls everything into one store and keeps it current.

On top of that is a dashboard built around the questions we actually ask — revenue and refunds, the funnel, churn, engagement, how launches perform. We refer to this literally every working day.

On top of the dashboard sits the Consultant, an AI analyst wired into the same sources. The dashboard covers what we already know to ask. The Consultant is for the questions we don't, and it can go and run the query itself rather than guess from a cached summary. The unglamorous part was teaching it to say "I can't reach that" instead of inventing a confident number, and most of the build is exactly that.

ATW Intelligence — the real-time dashboard
Real-time dashboard
ATW Intelligence — the Consultant answering with sources cited
Consultant layer
Internal · live

Next.js
Neon Postgres
SegMetrics

Ad-cohort viewer

How each campaign's leads behave once they're in our system.

We run a lot of different ad campaigns, and we are mostly satisfied with our attribution software's account of our RoAS. What it can't tell us though is what happens to those leads over time, in cohorts based on the campaign they came in from rather than the date they joined. This tool does that.

We can analyze by campaign or by presenter, which matters in our business. The app shows us how many of the leads who joined for a given launch bought (and when), how many bought again (and when) and how many have left the list (and when). This helps us prioritize our spend for the next launch, and gives us an adjusted RoAS picture based on lead quality.

Ad-cohort viewer — money collected
Money collected
Ad-cohort viewer — time to first purchase
Time to first purchase
Pale winter mist over the Ærø landscape
Internal · live

Next.js
Neon Postgres
Vercel
Clerk

ATW Adapter

Our Zapier stack, replaced by one service.

The course platform, the CRM, and the tools around them used to talk through a stack of Zapier automations — about fourteen, added one at a time over the years. Any single one was reasonable. The pile was not: it failed in ways nobody could trace, and it billed us every month for the privilege. The Adapter replaced all of it with one service I own.

Underneath it there's one rule: write the event down before touching it. Everything else happens only after the event is safely stored, so a bad deploy or a slow third-party API can't lose it. Adding a new course is a row in a table now, not a new automation. And when something breaks, it pings my phone instead of sitting in an inbox nobody reads.

The rollout was a key part of the build. For a full month before it went live, it ran in shadow mode — handling real events alongside Zapier and logging what it would have done — so I could compare the two before switching anything over. It shipped in seven stages. All of them are live.

ATW Adapter — the event log
Event log
ATW Adapter — the course registry
Course registry
Internal · live

GitHub Actions
Neon Postgres
Ontraport

Invitation Engine

Evergreen email routing brain, moved out of the CRM.

The company sends a lot of automatic email — evergreen invitations that fire based on where someone is with us. That logic used to live inside the CRM, which is fine until the rules get complicated. Then the CRM becomes a place where decisions happen that nobody can see, and changing anything means poking at automations and hoping.

The Invitation Engine takes the deciding out of the CRM. A job runs every night, reads the whole list, works out who should be invited to what, and hands the CRM a finished list to send. The CRM stops thinking and just sends. The rules live in one repo now, where I can read them, test a change before it goes out, and give a straight answer to "why did this person get this email."

Invitation Engine — a nightly assignment run
Nightly assignment run
Warm afternoon light across the fields in June
Internal · live

Next.js
Neon Postgres
Google Calendar API

ATW Project Hub

Where the next courses get planned.

This is where the team plans what it's making. It replaced a database tool everyone put up with and nobody liked. The middle of it is a launch calendar — what ships, and when — with the video editing pipeline hung off it as a board that moves as the work moves.

It syncs to Google Calendar so the schedule shows up where people already look, and it holds the planning for the membership's monthly programming. Most of the features got there by request: a director says "I wish I could see X," and the next week X exists.

Because it's internal, I let it have a personality. It's warm and a little silly, with some light gamification, on the theory that people keep a tool up to date when they don't mind opening it. For an internal tool, that beats almost any feature you could add.

ATW Project Hub — the launch calendar
Launch calendar
ATW Project Hub — the video editing pipeline board
Video pipeline
Internal · live

Next.js
Claude
Automated crawl

Preflight

The pre-launch check that reads every page.

Before a launch opens, someone is meant to click through every order form and landing page and make sure the price is right, the buttons work, and the copy isn't left over from last year. On a big launch that's several hundred pages. In practice nobody does it properly, because it's tedious, we're often in a rush, and the checklist alone is several pages long.

Preflight does it instead. It's an agent that walks the funnel one page at a time and reports what's wrong: a price that doesn't match the offer, a checkout wired to the wrong product, a stray line of old copy. Its first real pass turned up the company's own domain misspelled on twenty-two pages, and price or copy mismatches across roughly five hundred order forms — the kind of thing that costs sales for weeks before a customer finally emails to point it out.

This build was one of the most painful so far: my first draft had excellent plumbing, but I left the UI for last, and on a tool that's meant to be read at a glance by someone in a hurry, the display of the information is nearly as important as its accuracy. My second draft tried to rescue the first and failed. On the third time around, I started by making my ideal UI, then I wired it up.

Preflight — the report of issues found on a run
Issue report
Preflight — a single flagged page with the problem shown
Flagged page
Evening light over Ærø in May
Personal build

Next.js
Neon Postgres
Whisper
Claude

herhjemme

Homeschooling, on record, without the admin.

Herhjemme ("here at home") is a homeschooling app I built for one family: mine. Its first job is to turn a six-year-old's ordinary days into a record without anyone stopping to write anything down. Voice notes, photos, and half-sentences go in whenever; the transcribing and filing happen later, on their own.

The reason it was worth building is the second view. Danish home education is supervised by the local kommune, so a year of learning has to be handed back as documentation. The app keeps two versions of the same days: one that reflects how a week actually went, and one that maps those same events onto what Ærø Kommune wants to see. You capture once and get both, plus a PDF in Danish at the end.

It suggests activities too, but only from material we picked ourselves, and a values file in the repo overrides everything else if they ever conflict. It's a personal thing, not a product, so every call goes to the simplest version that works.

herhjemme — ambient capture of a day's activity
Ambient capture
herhjemme — the Danish supervision export
Kommune export
In development

Next.js
Neon Postgres
Bunny Stream
Clerk

Courses app

The course platform, owned instead of rented.

The Academy rents its course platform now. This is the replacement — video hosting, search across the whole library, and the experience of working through a course, built around how clinicians actually study.

The goal is to make an app that embodies the future of online learning: an AI study helper, custom learning journeys, reflection questions, knowledge checks, diagrams, and digestible-sized videos all generated automatically from a single Google Drive upload. Plus a fully integrated community feed.

Courses app — the lesson view with transcript and scaffolding
Lesson view

Next

Most of these began as something I needed on a Tuesday.

If you're deciding whether to build an internal tool or just buy another subscription, I'm glad to talk it through — including the times buying wins.

ian@imcpherson.com