Archive:

Subscribe:Atom Feed

DoMe

Posted: 01 April, 2026

As a Gentleman of a Certain Vintage I'm long past the point of being able to rely on myself to remember things. And if I'm honest, it's never a skill that I've ever really had, thanks to a predilection toward the Morroccan. I've been using ToDoist quite happily for the last few years and it does the job. It works on the web and my phone, so it's always to hand. But like everything else, they've decided to shoot the price up. 48quid a year always seemed a bit high, but I was happy to throw some coin to a service I liked, and keep them going. But 60 quid? Naaah. Not for my needs.

So. How hard could it be to write a ToDo list app in 2026? Something that could sit on the server in my office, be accessible over my Tailscale, and yet still have the ability to ping my phone when I'm supposed to remember something? Not that hard, as it happens. Especially if you write it like it's 1998.

The Stack

The backend is a CGI application written in C -- yes really, I like small fast things -- whose only dependencies are libc and SQLite. It's a mahoosive 108KB.

For the front-end, I've hacked up something in vanilla JavaScript, that I can host simply and easily under an existing Apache server. It connects to the backend via a fixed REST API.

Why C, again?

Old man is old. So, partly for lols, partly because the first thing I did on the web was CGI (although that was in Perl, admittedly), and partly because it's a natural fit. Each request is a fresh process, so there's no connection pooling, no memory leaks across requests, and no state management. The process starts, reads the request, queries SQLite, writes JSON to stdout, and exits. I love the simplicity.

SQLite is handling any possible concurrency through WAL mode and immediate transactions, which should be fine for something that's only ever going to be used by me.

Why CGI?

Well, for a start, it's funny. But why not? CGI gets a bad reputation for being slow, but "slow" is relative. Each request forks a process and runs my little C binary, which takes a few milliseconds, which is nice and snappy for an app with one user, making a few requests per minute.

And it's simple. The "server" is a static binary that I can poke at, change and test, really easily. Apache handles everything else. There's no daemon to monitor, no port to configure, no "restart" to worry about. No packages changing beneath me.

The Database

Nothing clever in this, it's just three tables: Category, Todo, and Reminder. Todos are soft-deleted so they can be recovered. Reminders are linked to Todos, with cascade deletions. The schema uses partial indexes for common queries: active todos, sorted by priority & pending reminders, sorted by time.

The API

REST over JSON with Categories and Todos supporting full CRUD. Todos can be filtered by category, done status, overdue, and upcoming. Reminders are managed as sub-resources of Todos.

The Frontend

The UI is a single web page that talks with fetch, iterates over the returned data, and makes a bunch of Cards that I lay out with CSS. It's got the usual filters for Active, Pending, Done and Archive, and allows me to add a new Todo, with reminders. Which, if I'm honest, was the most time consuming thing in the whole project. Date & time pickers are still a monumental ballache to layout and get right. Who knew?

Reminders and Notifications

Tbh, the real meat-and-potatoes in the whole thing... How in the hell do I get my own self-hosted ToDo list to ping my phone with a reminder? I wouldn't have bothered with ANY of this if there wasn't an easy answer: ntfy.sh

ntfy.sh is a pub/sub notification service -- you publish to a topic, and anyone subscribed to that topic receives the message. Yes, atm anyone could be getting my ToDo reminders, but the great thing about ntfy.sh is that it can be self-hosted. And that's currently the top of my DoMe ToDo list.

ntfy.sh Just Works. Does exactly what it says on the tin, and I cannot praise the whole thing enough. 10/10, no notes.

I made another small tool called dome-remind that runs every minute via cron. It queries the database for reminders with a time in the past, then fires each one out as an HTTP POST to ntfy.sh. The notification includes the todo heading, category, and priority level.

There's also a nice little iOS app that lets you subscribe to a topic. Do that, and notifications arrive on your phone, just like any other app. Boom!

Desktop

Phone notifications are cool, but I tend to have mine on silent most of the time, so I need something on my laptop. I wrote a tiny little bash script that connects to ntfy.sh's JSON streaming endpoint and calls notify-send -- which seems like the standard Linux way of popping up a notification. The audio side is handled by paplay. I've stuck it in a systemd user service, started at login, that reconnects automatically if the connection drops.

This took me a while to get going, cos it was all new stuff, but the end result was a tiny little script that seems to be working. So far.

Was it worth it?

In purely financial terms -- compared to a ToDoist sub -- then possibly not. At least, not if you factor in my day-rate. But... Like the static site builder I wrote to spit out this website (and my company one) it's mine. I can tweak it, fiddle with it, and nudge it toward exactly what I need, whenever I need to. That's got a lot of value. Besides, it was a fun little diversion for a few evenings.

I'd like to add recurring todos and a way to snooze reminders from the notification itself. But for now, it does what I need: a reliable (fingers crossed) to-do list that actually reminds me to do things!

And it's CGI baby! Which makes me smile. A lot. :D

Previous Post: PXN VD6 Bundle