Case study · Internal tooling at a mid-size media company
The inbox I stopped opening
A script that runs when I unlock my laptop, works out which code reviews still actually need me, and has them open in one window before I sit down — so the inbox stopped being a place I go in the morning.
- Tabs waiting each morning
- 5–12
- Measured — typical range. It moves with how much the team put up the day before.
- Running unattended
- since 6 May 2026
- Structural — triggered by the first unlock of the day.
- Times it runs per day
- 1
- Structural — the trigger fires on every unlock; the work is guarded to once, with an explicit override.
The problem
Seven steps to look at one thing
Code review requests arrive as email. Here is what I did with them, every morning, for years: open the mail client. Click into the label where the review notifications collect. Click into a message. Work out whether that review is still open or whether it was merged overnight and the message is now archaeology. If it is still open, click the link. Watch it load in a new tab. Drag that tab into the particular window I keep my reviews in. Go back. Do it again for the next one.
Five to twelve times, depending on what the team put up the day before. None of it is thinking. All of it happens in the first ten minutes of the day, which is the part of the day I would most like to spend on something else, and by the end of it the only thing I have produced is a browser window in a particular arrangement.
That last observation is the one that mattered, and it took me an embarrassingly long time to notice. The output of all that clicking was never information. I already knew there were reviews waiting. The output was a window with the right things in it.
What I built
The morning arrives already open
A script runs when I unlock the laptop. If it has already run today it stops immediately. Otherwise it reads the last twenty hours of mail, keeps the messages matching a small set of patterns, pulls the action link out of each one, removes duplicates, caps the total, and opens what is left as tabs in a single window.
I open the laptop and the window is there. There is no digest, no notification, no summary to read and act on. The state I used to assemble by hand is simply the state the machine is in when I arrive.
The matching is pattern-driven rather than hard-coded, so adding a new kind of notification is a line of configuration rather than a change to the program. That was not cleverness, it was self-defence: the first version had the patterns baked into the code and I stopped extending it, because every new source meant opening the source.
Architecture
Four steps, no database
The hard part
Knowing what not to open
The obvious build here is an email classifier that sends you a summary of your morning. That version is worse than doing nothing, and understanding why is most of the design.
A summary is another thing to read
A digest converts a pile of email into a smaller pile of email. It still arrives, it still has to be read, and it still ends with me clicking things. The genuinely useful output was never information about my reviews — it was the reviews, open, in the window I already look at. Once you accept that the deliverable is an arrangement of your environment rather than a document, the whole shape of the tool changes: there is no report to design, no digest format to bikeshed, and no notification to dismiss. Success is that a thing you were going to do by hand has already happened.
I think this is the most portable idea in the tool. A great deal of internal tooling stops one step short of useful, because summarising is easy to build and putting the system into the state the person actually wanted is not.
The trigger fires all day; the work happens once
The natural trigger for “my morning” is unlocking the laptop, and the problem with that trigger is that I unlock the laptop constantly. After lunch, after a meeting, after walking away for coffee. A tool wired directly to that event would rebuild my morning six times a day.
So the run is guarded by a single stored date. If the flag already says today, the script exits before it does anything at all — no mail read, no tabs, no cost. An explicit override flag re-runs it for the days I do want the set back. It is the same instinct as the dedup in the merge handler, applied one level up: there, the unit that must not repeat is the item; here it is the whole morning.
A time window instead of a memory
The obvious way to stop a week-old review reappearing every morning is to remember every review you have already been shown. That means state, and state means a file that can drift, grow, and be wrong in ways you only discover on the morning it hides something.
There is no such list here. The query simply looks back twenty hours, which is wide enough to catch everything since yesterday and narrow enough that nothing old comes with it. The window does the work the memory would have done, and it cannot get out of sync with reality because it does not store anything about reality. I like this kind of trade a great deal: not every filter needs to remember, and the ones that do not are the ones still working a year later.
Limitations
What I’d do differently
It does not check whether a review is still open. My manual version did: I would read the message, work out whether it had been merged overnight, and only then click. The tool skipped that step, so a review completed while I slept still becomes a tab. In practice the twenty-hour window keeps that rare enough to live with, but it is a step I used to do and the machine does not, and I would rather write that down than pretend the automation is a strict superset of the human process. It usually isn’t.
The bigger gap is that silence is ambiguous. It announces success — a small notification saying how many tabs it opened — but when there is nothing to open it exits quietly, and when something breaks it also exits quietly, into output nobody sees because the whole thing is launched hidden. So “a calm morning with no reviews” and “the mail query has been broken for a week” look identical from where I sit: no window, no notification, nothing. For a tool whose entire job is to tell me what needs attention, that is the wrong default, and it is the first thing I would change.
It also inherits its trigger from a habit. Running on unlock is exactly right for someone who opens a laptop each morning and wrong for anyone whose day starts some other way — a reminder that this is a personal tool that happens to generalise, not a product.
Where this applies
Deliver the state, not a report about it
Most teams have a queue that arrives as notifications about work rather than as the work itself: review requests, tickets assigned overnight, failed jobs, approvals waiting. The standard response is a dashboard or a digest, and the standard result is one more surface nobody reads.
The better question is what state the person was assembling by hand at the end of all that clicking, and whether the machine can simply put them in it. Often it can, and it is less work than the dashboard.
The part worth stealing is how little state that takes. A notification is only true as of when it was sent, so a tool built on notifications has two options: verify each one against the system that owns it, or bound the window narrowly enough that staleness stops mattering. The first is correct and costs an integration. The second is a single number in a config file. I took the second, and the honest reason is that the failure it allows — occasionally opening something already dealt with — costs me a keystroke, while the state file I avoided would have cost me a class of bug forever. Pick which of those you can afford before you build the clever one.