You're viewing the AI side. See the human side
Vikram Bhalla

Minutehand

This is an AI-built project. Switch to AI mode to read the full story.

This project page is best viewed in AI mode, where I go into how a small macOS app got built, signed, notarised and sold.

Use the toggle in the navigation to switch modes, or click here to view in AI mode.

Minutehand

A menu-bar app that records your meetings and files the transcript in a folder your AI agent already reads. No bot joins the call. No subscription. The first thing I've built that people pay for.

Everything else on this page is something I built and gave away, or built and kept. Minutehand is the one people pay for. That single fact changed how I had to think about all of it: the code, the licence checks, the notarisation, the refund policy, the sentence on the website that has to be true.

The Ferrying Problem

My AI agent knows a great deal about my work. It has my files, my project notes, my half-finished drafts. What it doesn't have is any idea what was said in the meeting I just finished.

So the ritual goes like this. The notetaker records the call. It produces a summary written by a model that knows nothing about the project. I ignore the summary, dig out the raw transcript, copy it, and paste it into the agent that actually has the context. Then I ask my real question.

I watched myself do this perhaps forty times before it occurred to me that the copying and pasting was the whole product. Everything else the notetaker did, I was throwing away.

Minutehand does the small thing. It records, transcribes, and writes a Markdown file into a folder on your Mac. Claude Code, Cowork, Codex, whatever you use — they read folders. The transcript is simply there the next time you ask.

Where It Started

It started as a tool for one client team, not as a product.

Part of my AI consulting work involves sitting with an organisation and finding the places where time and knowledge quietly leak out. One of the six we found was client calls: fifteen people having conversations full of decisions and commitments, with the record of each one living in whoever happened to take the best notes. Three months later the project is over and nobody can reconstruct what was agreed in week two.

I built them a menu-bar recorder to close that gap. It worked. Then I realised I wanted it myself, and that the ferrying problem I'd been complaining about for months was the same problem with a different shape. So I rebuilt it properly, gave it a name, and put it on sale.

Building for a real team before building for a market turns out to be an enormous advantage. Every feature request I received in the first version came from someone who had to use the thing on a Tuesday morning with a call starting in four minutes. There is no better filter.

No Bot Joins the Call

Most meeting tools work by sending a bot attendee into your call. It shows up in the participant list with a name like "Otter.ai Notetaker", everyone glances at it, and someone always asks what it is.

Minutehand records the microphone and the system audio directly from your Mac and mixes them into one file. Nothing joins anything. It works with Zoom, Meet, Teams, a phone on speaker, or two people talking in a room, because it has no idea what a meeting platform is. It just hears what the machine hears.

The app has no window and no Dock icon. It lives entirely in the menu bar: start and stop from the tray, or from the notification that appears just before a meeting begins. There's a small floating pill while recording so you never have that moment of wondering whether it's actually running.

It watches Google Calendar — polling every sixty seconds, filtering for events that look like actual meetings, honouring an exclusion list for the recurring ones you never want recorded — and prompts you a few minutes before each one starts. That prompt is doing most of the work. A recorder you have to remember to start is a recorder you use twice.

On privacy I have to be precise, because the marketing copy for this category is full of comfortable half-truths. Recordings and transcripts are written to your Mac and stay there. There are no accounts and no telemetry. But transcription is not on-device: the audio goes to Deepgram, through the user's own API key, and comes back as text. I say so plainly on the website. The honest version of the claim is still a good claim, and the dishonest version is the sort of thing that ends badly.

Zero Dependencies

The package.json has no dependencies field. Only devDependencies. Every line of runtime code that ships is either mine or Electron's.

This was a deliberate constraint and I nearly abandoned it about six times. Each abandonment was tempting for the same reason: the thing I needed was thirty lines, and there was a package that did it in one import. But this is an app that touches the microphone and the system audio of the machine it runs on. Every dependency is a supply chain, and I didn't fancy explaining a compromised transitive dependency to someone who paid me $39 for a recorder.

The constraint also paid off somewhere I didn't expect, which I'll come to.

The one place I did spend effort on robustness is the recording itself. Audio is chunked to disk continuously as it records rather than held in memory and written at the end, so a crash costs you the last few seconds instead of the last ninety minutes. The transcription jobs go through a crash-safe queue with retry, so closing your laptop mid-transcription or losing your connection on a train doesn't lose the recording. Old audio is deleted automatically after a retention window you set.

There's exactly one automated test, on the pure function that turns a Deepgram response into Markdown, with hand-written fixtures and no network calls. I'd like to claim a principled testing philosophy. In truth that's the module I broke most often, so that's the module that got a test.

The Hard Parts

The universal build. Shipping one download that runs on both Apple silicon and Intel means building twice and merging the results with @electron/universal. That merge only works cleanly when the two builds contain identical files. Any native compiled dependency differs between architectures and the merge falls over, at which point you're hand-annotating which paths are architecture-specific. My zero-dependency rule, adopted for security reasons, made the universal build a non-event. Constraints have a habit of paying out somewhere other than where you set them.

Notarising the disk image. electron-builder signs and notarises the .app inside your DMG and then stops. Gatekeeper rejects an unsigned disk image even when the app inside it is perfectly notarised. So the first thing a customer sees, after paying, is macOS refusing to open what they bought. The fix is a second signing and notarisation pass over the DMG wrapper itself, which is now a script in the release chain. Finding this out cost me an afternoon and a genuinely unpleasant few minutes of imagining it happening to a stranger.

Licence checks that don't punish the innocent. The obvious way to validate a licence is to call the API and trust the answer. The obvious way is wrong, because it means a flaky café connection turns a paying customer's app into a brick. Validation is offline-tolerant: only a definitive rejection from the licensing API — a 403, 404 or 422 — demotes a licence. Network errors and server errors never do. An invalid licence blocks starting a new recording, and never interrupts a session already in progress. Nobody is going to lose a client call because my server had a bad minute.

Secrets that never cross the wire. The Deepgram key, the licence key and the Google tokens are all encrypted at rest through macOS safeStorage. None of them ever cross IPC to a renderer process — the UI receives a boolean saying whether a key exists, or a masked string, never the value. If secure storage isn't available on the host, the app refuses to save the secret at all rather than quietly falling back to plaintext. That last one is a real decision with a real cost: some users will hit a wall. I'd rather they hit a wall than a silent downgrade they never learn about.

Shipping It

Writing the app was the part I knew how to do. The rest was new.

Apple's Developer ID signing and notarisation. Google's OAuth verification process, which took a few weeks and a certain amount of patience for an app that only ever asks to read your calendar. A payment platform — Polar, in the end — with licence keys, a customer portal for re-downloads, and a webhook flow I verified by buying my own product with my own card to make sure the licence and the download both actually arrived.

A refund policy. An update mechanism, which is a JSON file on the site that the app checks when you ask it to, with no background polling, because an app that phones home on a schedule is not the app I said I was selling.

The pricing is $39 once, as an early-adopter price for the first hundred downloads, then $59. Lifetime licence, two Macs, seven-day refund. Transcription runs on your own Deepgram key, and a new Deepgram account comes with $200 of credit, which at the rate Minutehand actually uses — including speaker diarisation, which it always requests — works out at over three hundred hours of meetings. I checked that number against Deepgram's published rates rather than repeating the figure I'd first guessed at, and it turned out my first guess was nearly double the truth. Round down, always.

Where It Stands

v1.0.0 is built, notarised, tagged and on sale. The website has been live since late July. Google's OAuth verification came through in early August, which was the last external thing standing between the app and anyone who wants it. Requires macOS 13 or later, Apple silicon or Intel. Windows is in development.

Nothing gates it now except me telling people it exists, which is a category of work I am measurably worse at than building the thing in the first place. Two decades of doing this for other people's brands, and I still find my own the hardest brief in the room.