I put client KPIs on an e-ink screen instead of a dashboard nobody opens: why the artifact couldn't go there and a webhook could

The dashboard was fine. Opening it was the problem. Here is why a private, JavaScript-driven page can't go on an e-ink display, why I fed the screen from the pipeline that already existed, and why refresh rate is a claim about your data rather than a setting.

I put client KPIs on an e-ink screen instead of a dashboard nobody opens: why the artifact couldn't go there and a webhook could

I had built the client dashboard already. It was good. It was also a page on claude.ai that needs me to be logged in, and I was opening it roughly twice a week, usually because something else had reminded me to.

There is a small e-ink display on my desk. It shows the family calendar. My first idea was simply to put the dashboard on it, and that idea died in about four minutes: the device fetches a rendered image on a schedule, it does not run a browser, and it certainly does not carry my session cookie. A private, JavaScript-driven artifact cannot go there. Not "is awkward to put there" — cannot.

Which turned out to be the useful constraint, because it forced the actual question. Not "how do I get this page onto that screen", but "which three numbers should be in my eyeline whether or not I decide to look at them".

The dashboard you have to open is a different product

Everyone in this trade has built a beautiful dashboard that nobody opened, mine included. The failure is rarely the dashboard. It is that opening it is a decision, and a decision that competes with forty others loses most mornings.

A screen on a desk removes the decision. That is the entire difference and it is bigger than it sounds.

Cal Newport, in Deep Work, borrows the four-disciplines framing and lands on the one I keep coming back to: "Keep a Compelling Scoreboard." Not a report. A scoreboard — the thing that is simply there, visible, while you do the work it measures.

So the screens are deliberately thin. For the natural-stone supplier I look after: enquiries per day, marketing cost, cost per enquiry. For a home and living brand: orders, ad spend, ROAS. Three numbers each, with a week-over-week delta. That's it. Anything I would need to interpret does not belong on a glanceable surface — it belongs in the fuller dashboard where I can see what the number is made of.

Don't build a second path to the same data

The strong temptation here was to have the display fetch its own data. New API calls, new credentials, new failure mode, and — guaranteed, eventually — a screen that disagrees with the dashboard because one of them pulled at a different hour.

Instead the screens are fed by the pipelines that already exist. Both clients already have a scheduled refresh that pulls ad spend and orders or enquiries and writes the result. All I added was a final step that posts a payload to the device. The webhook plugin strategy takes a couple of kilobytes of merge variables and renders them against a template on the vendor's side, which means the push is a single HTTP call at the end of a job that was running anyway. One of them runs from a scheduled GitHub Action, which is also the only place that can reach that brand's live data, since it lives in a Cloudflare KV store I can't read from my laptop.

One source, two destinations. If the number on the wall is wrong, the dashboard is wrong too, and that is a much better property than two systems that are independently plausible.

Refresh rate is a claim about the data, not a setting

I set the enquiry screen to update every four hours and left the spend side alone, and that split is the bit I'd argue for hardest.

Enquiries and orders happen through the day; a four-hourly number is real. Ad cost is not — it settles once a day, and one of these clients gets marketplace spend through a reporting pipeline that runs a day or two behind. Refreshing a lagging number four times a day doesn't make it fresher. It makes it four times as likely you react to an artefact of the reporting lag. So the ROAS window deliberately ends on the last day with complete spend, and the screen shows that, rather than a more recent number that is quietly incomplete.

Same reasoning on the comparison. Both screens show a week-over-week delta, not a thirty-day prior period, because on one of them the tracking only started five weeks ago and a thirty-day comparison would be measuring the instrumentation rather than the business. I've made the mistake of reading a summary number without checking what it could possibly have seen often enough to have a rule now: before a number goes on a wall, say out loud what window it covers and what it is blind to. If you can't, it isn't ready to be glanced at.

What it found in the first week

The thing I hadn't expected. Within a day of the second screen going up, I could see that the seven-day ROAS had dropped below the band we'd agreed as the target, while the thirty-day figure — the one in the monthly reporting — was comfortably inside it.

Both numbers were correct. The monthly one would have shown me the same thing in about three weeks, at which point the question would have been "what happened last month" instead of "what changed on Tuesday". That is the entire value proposition of a scoreboard and I got it for the cost of an afternoon.

The unglamorous notes, since this is a field note: Python's urllib on the build I was using has no CA bundle and fails SSL verification outright, so the push shells out to curl. And the template editor does not save on Cmd+S, which cost me one round of confused debugging before I noticed the button. Both of those are the kind of thing that turns a two-hour job into a four-hour one, and neither is in any documentation.

Where this generalises

I don't think the lesson is "buy an e-ink display". The lesson is that a measurement system has a delivery problem as well as a calculation problem, and we almost always spend our effort on the calculation.

The question worth asking on any reporting build is: where will this number be when the decision gets made? If the honest answer is "behind a login, two clicks away, in a tab that isn't open", then the accuracy of the number is close to irrelevant. It doesn't get consulted. Getting it into the place where the person already looks is not a nice-to-have on top of the analytics work — for a lot of numbers, it is the analytics work, and it's the half that is usually left undone.

I have built a great many dashboards. The one that changed a decision this month cost me one HTTP call at the end of a job I had already written.

Sources & further reading

External: TRMNL — private plugins, webhook strategy · GitHub Docs — events that trigger workflows (scheduled runs) · Cloudflare Workers KV documentation · FranklinCovey — the 4 Disciplines of Execution

Related posts: Enquiry value, not revenue: naming the number on a client dashboard · I scheduled a weekly check that could never run · Check product-query fit before cutting a campaign · I replaced Loom with a single HTML file

Subscribe to Remco Livain

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe
Work with me →×