Taskmill: Shared Lists for Google Tasks

#Tools

Running a few rentals means a steady stream of small jobs: replace the smoke detector battery, call the pest company, renew the permit. Until now, your options to handle the onslaught of tasks were limited:

  • You could use emails, and drown in the deluge of emails and updates.
  • You could use a spreadsheet, and now you have a yet another freeform table to care for.
  • You could use an existing task sharing solution, and find that you can’t organize tasks as you wish.
  • You could try using a separate app, and find yourself copying and pasting between email and the app.

It is as if people who work on these task apps don’t use tasks themselves. So I had a tool built to do it right: Taskmill. It is currently working on our own data, and I am going to make a best effort to release it for everyone to use.

What it does

Taskmill gives a group of people shared tasklists which are surfaced in gmail. If you use gmail, you are already all set up to use it.

So far, not much is different from any other task tracking method. Here is the rub.

Each shared tasklist shows up in every member’s Google Tasks. You can look at them or ignore them. This is the shared view. However. When a task is assigned or shared with you, a task copy appears in your own tasklist, usually the “default” one.

If the group keeps a task spreadsheet, it can be synced to as well. This is entirely optional. But, if you need to buik-edit tasks, this is what you can use. No separate UI for handling buik edits, just a spreadsheet.

Taskmill keeps all the task copies in step: the title, the notes, the due date and whether it is done. You modify these attributes by editing the task text, instead of requiring dedicated UI elements. You type instead. If you are a fast typist, you might like this arrangment.

Your own copy is yours to arrange. Move it to any of your lists, star it, put it in whatever order you like. None of that reaches anyone else.

This is the most useful way to share task items: there is an aspect of each task which is “shared” but there is an aspect of each aspect which is “personal”. In Taskmill, these two are modeled as separate tasks, which are kept partially in sync using a shared identifier. However, private adjustment to the private part of the task remain yours to arrange as you will.

Two things do reach everyone. Notes sync, so a note you add to your copy shows up on every copy. And the tasks in the shared group list belong to the group: moving one out of that list takes the task out of the group for everyone.

  • A group gets a list in every member’s account. Make a group, say @family, and Taskmill adds a Google Tasks list of that name to each member’s account. Edits, completions and deletions sync in both directions.
  • Assign a task by writing #to: in its notes. Write #to:jo in a task’s notes, using Jo’s Taskmill name, and the task goes to Jo. Jo gets a personal copy in their default list, plus a reminder on their calendar. The personal copy and the group’s copy stay in sync both ways.
  • A group can sync with a Google Sheet. You link the group to a tab in a sheet. Each row syncs with a task, both ways. The one required column is the task description; Taskmill also fills in columns such as assignee, target date, status and notes. Taskmill keeps your sheet’s columns and formatting. It keeps its own IDs in hidden sheet metadata, so nothing extra shows up in your cells.
  • A small web app handles setup. You sign in with Google to set up groups, change settings and read an activity log. Taskmill asks for as little access as it can. For sheets, that means access only to the files you pick.

One rule shaped the design: Taskmill never writes the text of your tasks to disk. Its database holds IDs, states and timestamps. Its logs hold task IDs and nothing else.

How this differs

Shared to-do lists are not new. Here is how Taskmill differs from the usual options.

  • No Google Workspace needed. Taskmill works with ordinary Google accounts, the free gmail.com kind. Google’s own way to assign a task from a document is part of some paid Workspace plans.
  • Nobody has to open anything new. There are no plugins, add-ons or browser extensions, and no new account or password. You sign in with Google once at taskmill.appreise.com, so Taskmill can reach your tasks, and after that your Gmail is enough. Your tasks stay in Google Tasks. They show up where Tasks already does: the Tasks panel in Gmail and in Calendar, tasks.google.com, and the Tasks app on your phone. The spreadsheet is there if you want it, but you never need it. Taskmill’s web page is for setup only.
  • Google’s own options do not fit. Google’s shared tasks live in a Chat space, so you go to the space to see them. Google Keep can share a note with other people, but you share the whole note, so a checklist is shared all or nothing. That is too coarse for tasks. Keep also sits in Gmail’s side panel, but I find that clumsy at best.
  • No other app. Separate to-do apps and project boards mean one more app, and everyone you share with has to use it too.
  • Sharing works one task at a time. Write #share:jo in a task’s notes and that one task goes to Jo. #share:jo,sam sends it to two people, and #share:@family to a whole group. Many to-do apps share a whole list or project instead: in Microsoft To Do you send an invitation link to a list, and in Todoist you share a project before you can assign anything in it. Taskmill has a shared group list too, for those who want one, but nobody has to use it.
  • You control tasks by typing. Short markers in a task’s notes do the work: #to: assigns it, #share: shares it, #p: puts it in a project, #status: sets its status, #r: makes it repeat, and #every: reminds you until it is done. You type them wherever you type the task.

How it was built

The stack, for those who care: Go, built with Bazel so that it does not depend on tools installed on the host machine. The browser gets plain HTML pages, made with Go’s html/template, htmx and Pico CSS, with no JavaScript build. A web front end and a core sync service talk over Connect RPC, and the data lives in SQLite. Even the design document is a LaTeX paper built by Bazel. The LaTeX packages it needed became their own public module, texlive-packages.

The first milestone had no product code at all. “Milestone 0” was a probe program. It ran against real accounts on throwaway lists, to learn how Google Tasks actually behaves before anyone designed anything. It did not catch everything, as the stories below show.

Three lessons from syncing with Google

A moved task keeps its ID

Google Tasks keeps a task’s ID when you move it to another list. Taskmill first tracked tasks by ID. So when someone moved a task out of the group’s list, Taskmill still found it, and it showed up on the group’s sheet.

The fix was to sync by list membership: a task belongs to the group while it sits in the group’s list. That raised a product question, not a technical one. What should happen when you move a task out? The first answer was to move it back. Then we found that Google’s move call has no “only if unchanged” check, unlike edits and deletes, so a move could silently lose an edit made at the same moment. I decided against moving tasks at all. A task you move out now leaves the group for everyone: the other members' copies are deleted, its row in the sheet is removed, and the task stays where you put it, as an ordinary personal task. Everyone in a group should see the same list.

Linking a sheet can double everything

Someone linked a sheet whose rows already matched the tasks in the list. Taskmill saw tasks with no rows and rows with no tasks, and added each to the other side. Every task showed up twice.

The fix came in three parts:

  1. A one-off cleanup command. It did a dry run first and reported what it would change. It applied the changes only after I said yes, and it refused to run unless syncing was paused. When it stopped partway, a second run finished the job. Where the sheet and the list disagreed, the sheet’s values won.
  2. A new way to link a sheet: match, then preview. Taskmill pairs rows with tasks by title, shows you the counts, and writes nothing until you confirm.
  3. Pacing. The cleanup ran into Google Sheets’ rate limit once. The obvious answer is to retry. But retrying a flood of writes makes the flood worse. So Taskmill now paces its own writes first, at 50 a minute per user where Google allows about 60, and retries only as a fallback.

The daily quota is a budget

Google gives each app a fixed number of API calls per day. Taskmill has to check every list for changes, and checking too often burns through the quota. A quota planner spreads the checks across the day so that they use about 80% of the daily quota. It shares that budget among users according to how active each one has been, and every user still gets at least one check a minute.

Where it stands

Taskmill runs at taskmill.appreise.com. For now it is invitation only: each Google account is invited by hand, and there is no open sign-up. If you are interested in testing it out when it is ready, let me know.

If your household, your co-hosts or your cleaning crew already live in Google Tasks, Taskmill gives you one shared list without asking anyone to change apps.

As always, this is what worked for us, not legal, tax, or investment advice.