Friday, July 11th, 2025
I’ve tried them all. The slick, venture-backed to-do list apps with collaborative features I don’t need. The minimalist plain-text systems that are too simple. The web-based platforms that hold my data hostage in the cloud. None of them ever stuck. My needs are simple, but specific: I want a task management system that is text-based, version-controllable, and most importantly, pushes my agenda to me so I don't have to remember to check it.
Frustrated with off-the-shelf solutions, I decided to build my own. The result is Perennial Task, a simple yet powerful task manager written in PHP that runs on my personal server. It’s the backbone of my personal organization, and it ensures I never forget a bill payment, a renewal, or a simple chore.
Here’s how it works.
At its heart, Perennial Task is a collection of command-line scripts that interact with a directory of simple XML files. Each file represents a single task. The main entry point is a script called prn
, which acts as a controller, validating that the requirements are installed and then dispatching commands to the appropriate PHP script.
The interface is straightforward:
$ prn [command] [argument]
The primary commands are:
create
: An interactive wizard to create a new task.describe [task_file]
: Shows a detailed, human-readable summary of a single task.edit [task_file]
: Allows for changing any detail of an existing task.complete [task_file]
: Marks a task as done, either deleting it or updating its next due date.report
: The powerhouse of the system, generating a summary of what's due, overdue, and coming up soon.The system supports three distinct types of tasks, each stored in its own XML file. This structure makes it incredibly flexible.
This is your basic, one-off to-do item. It has a name and nothing else. It exists until I complete it. For example, my car_brakes.xml
file is a simple reminder.
<?xml version="1.0"?>
<task>
<name>Check Car Brakes</name>
</task>
This is for tasks with a hard deadline, like renewing a subscription or paying a bill. The key here is the <preview>
tag. This tells the system how many days in advance I want to be reminded. For my Home Trust credit card payment, I give myself a 10-day heads-up.
<?xml version="1.0"?>
<task>
<name>Home Trust Visa Payment</name>
<due>2025-07-24</due>
<preview>10</preview>
</task>
These are for the regular chores of life. Instead of a fixed due date, they have a <completed>
date and a <duration>
in days. The system calculates the next due date automatically. For instance, I need to make a call with my Fongo number every 45 days to keep it active.
<?xml version="1.0"?>
<task>
<name>Make Fongo Call to Stay Active</name>
<recurring>
<completed>2025-05-26</completed>
<duration>45</duration>
</recurring>
</task>
This is where the magic happens. I don't want to remember to check my tasks. I want my system to tell me what’s important right now. To achieve this, I have a simple cron job that runs a script called agenda.sh
every morning.
#!/bin/bash
PATH=/bin:/usr/bin
# Set the subject and get today's date
subject='Agenda For '
date=`date +%a,\ %b\ %d`
# Get calendar events from 'remind' and tasks from Perennial Task
calendar=`remind /path/to/my/calendar/file`
todo=`php /path/to/perennial-task/report.php`
separator="
---------- Tasks ----------
"
result="$calendar${separator}$todo"
# Exit if there's nothing to report
if [ "$result" = "No reminders.${separator}" ]; then
exit;
fi
# Format the email body
mail="From: \"Agenda Mailer\" <agenda@arthurdick.ca>
To: \"Arthur Dick\" <arthur@arthurdick.com>
Subject: $subject$date
$result
"
# Send the email using curl and my SMTP provider
echo -n "$mail" | sed ':a;N;$!ba;s/\n/\r\n/g' | curl -s \
--url 'smtps://smtp.fastmail.com:465' \
--user 'user:pass' \
--mail-from 'agenda@arthurdick.ca' \
--mail-rcpt 'arthur@arthurdick.com' \
--upload-file -
This script does two things:
remind
, a separate calendar program, to get my appointments for the day.report.php
from Perennial Task, which generates the crucial task list.The report.php
script is smart. It scans all my task files and tells me what is:
preview
window.The script then combines this information and emails it to me. This morning, my agenda email looked like this:
Subject: Agenda For Fri, Jul 11
(Calendar reminders would appear here)
---------- Tasks ----------
OVERDUE: Make Fongo Call to Stay Active (was due 1 day ago)
OVERDUE: Check Mail (was due 5 days ago)
DUE TODAY: Check Car Brakes
This email is my daily battle plan. It tells me exactly what I need to focus on without me having to open an app or visit a website.
After receiving my email, I see the Fongo call is overdue. I make the call and then update the task.
$ prn complete
The script presents me with a list of my tasks. I select "Make Fongo Call to Stay Active." Because it's a recurring task, it doesn't just disappear. It asks me for the new completion date.
--- Select a task to complete --- [1] Annual Doctor Checkup [2] ArthurDick domain extend [3] Cancel Gemini Subscription ... [13] Make Fongo Call to Stay Active Enter #, (q)uit: 13 Will this task recur? (y/n): y Enter new completion date (YYYY-MM-DD, press Enter for today): Task 'Make Fongo Call to Stay Active' has been updated with a new completion date of 2025-07-11. Completion process finished.
Now the task is updated, and it won't appear on my report again for another 45 days.
Building Perennial Task was more than a technical exercise; it was about creating a system that molds to my way of thinking. The benefits are clear:
agenda.sh
script itself is a perfect example—a custom integration that makes the core components even more powerful.In a world of overly complex and proprietary software, there's a unique satisfaction in building a simple tool that perfectly solves a personal problem. Perennial Task isn't for everyone, but for me, it's the perfect, noise-free system that keeps my life on track.
Tags: software developmenttask management
← Beyond the To-Do ListWhy I Ditched Taskwarrior →