Evernote Replacement: Bill Payment Workflow

Along with my friend Ton, I am seeking to emancipate myself from Evernote, a very capable cross-platform organizational app that I’ve been using for many years. The emancipation is part regaining-data-sovereignty and part (a related) worry about Evernote’s future, and how I would be stuck if Evernote disappeared.

The most serious day-to-day integration of Evernote into my life is using it to manage my household and office bills.

When a new bill arrives–mostly by email, these days–I create a new Evernote note, attach a due date (a week before the actual bill’s due date, to give me some breathing room) and drop it in the “Bills — Unpaid” Evernote category.

Here’s an example of this, showing my home heating oil bill with an alarm set for October 27, and my PEI Property Taxes bill’s final installment, with a reminder set for later in the month:

Screen shot of my Unpaid Bills category in Evernote.

Evernote sends email alerts, and Mac and Android notifications, on the due date I set, making it very hard to ignore the fact that I need to pay a bill when it’s due.

Once I’ve paid the paid–again, almost always online–I copy and paste the confirmation message from my credit union bill payment screen into the note, and move the note into the “Bills — Paid” Evernote category.

As you can see from the screen shot, I’ve paid 896 bills this way, going back to July 2010: this system has worked well, and I have an almost-100%-on-time bill payment record as a result.

To replace Evernote’s role in this, I needed a way to:

  1. Store the bills themselves–PDF files–with some redundancy built in.
  2. Set reminders to pay the bills.
  3. Record the payment information when I pay the bill.

I’ve come up with a system that uses Nextcloud, a PHP script, and the Mac Reminders application, and it works like this.

When a new bill arrives in my inbox, I do whatever’s needed to download a PDF version of the bill (usually this involves logging into a vendor website). I save the PDF to my computer.

Next, I right-click on the PDF to fire an Automator workflow that I wrote to process the PDF; the workflow looks like this:

Screen shot of Automator workflow to process bills

The workflow is simple: it takes the PDF file and passes it to a PHP script I wrote, /Users/peter/bin/bill.php, and, when the script is done, it has the Mac speak “I created a Reminder!”.

The bill.php script is where the heavy lifting happens:

First, I use pdftotext to convert the PDF to a text file:

function getTextFromPDF($file) {
	system("/usr/local/bin/pdftotext $file /tmp/pdfdump.txt");
	$text = file_get_contents("/tmp/pdfdump.txt");
	return $text;
}

Next, I use the text version of the PDF to find which vendor it’s from; I do this by looking for known strings in each bill, like the account number (I’ve removed my actual account numbers and replaced them with Xs):

function getBillType($text) {
	if (strpos($text, "XXXX XXXX XXXX XXXX")) {
		return "Eastlink";
	}
	else if (strpos($text, "XXXXXXX")) {
		return "Bell";
	}
	else if (strpos($text, "XXXXXXX")) {
		return "Kenmac";
	}
	else if (strpos($text, "XXXXXXX-XXXXXXX-XXXXXXX")) {
		return "MaritimeElectricStudio";
	}
	else if (strpos($text, "XXXXXXX-XXXXXXX-XXXXXXX")) {
		return "MaritimeElectricHome";
	}
}

With the bill’s vendor known, and assuming that the format of every month’s bill is the same, I can then extract the date of the bill, using what I know about how this appears on each bill:

function getBillDate($text, $billtype) {
	if ($billtype == "Eastlink") {
		// September 24, 2018 Account Number
		if (preg_match("/(.*) Account Number/", $text, $matches)) {
			return getNormalizedDate($matches[1]);
		}
	}
	else if ($billtype == "Bell") {
		// 5421473 9 September 21, 2018
		if (preg_match("/\n5421473 9 (.*)/", $text, $matches)) {
			return getNormalizedDate($matches[1]);
		}
	}
	else if ($billtype == "Kenmac") {
		// 10/4/2018
		if (preg_match("/\n(\d+\/\d+\/\d\d\d\d)/", $text, $matches)) {
			return getNormalizedDate($matches[1]);
		}
	}
	else if (($billtype == "MaritimeElectricStudio") or ($billtype == "MaritimeElectricHome")) {
		// Bill issued on 03Oct18 includes payments received
		if (preg_match("/Bill issued on (.*) includes/", $text, $matches)) {
			return getNormalizedDate($matches[1]);
		}
	}
	return FALSE;
}

I also need the bill’s due date: in some cases I look for the actual due date, in other cases the due date isn’t printed, and I calculate one based on a known amount of time after the bill date. I also set a reminder date, 7 days before the due date; as with Evernote’s reminder dates, this gives me some breathing room to pay the bill well before it’s due.

function getDueDate($text, $billtype, $billdate) {

	if ($billtype == "Eastlink") {
		// Please pay the total amount by Oct 15, 2018.
		if (preg_match("/Please pay the total amount by (.*)/", $text, $matches)) {
			$duedate = getNormalizedDate($matches[1], TRUE);
			$reminddate = strftime("%Y-%m-%d, 9:00 AM", strtotime($duedate) - (7 * 86400));
			return array($duedate, $reminddate);
		}
	}
	else if ($billtype == "Bell") {
		// please pay by: October 12, 2018
		if (preg_match("/please pay by: (.*)/", $text, $matches)) {
			$duedate = getNormalizedDate($matches[1], TRUE);
			$reminddate = strftime("%Y-%m-%d, 9:00 AM", strtotime($duedate) - (7 * 86400));
			return array($duedate, $reminddate);
		}
	}
	else if ($billtype == "Kenmac") {
		// No due date specified -- add 30 days to bill date
		$duedate = strftime("%Y-%m-%d, 9:00 AM", strtotime($billdate) + (30 * 86400));
		$reminddate = strftime("%Y-%m-%d, 9:00 AM", strtotime($duedate) - (7 * 86400));
		return array($duedate, $reminddate);
	}
	else if (($billtype == "MaritimeElectricStudio") or ($billtype == "MaritimeElectricHome")) {
		if (preg_match("/Payment Due On or Before: (.*) Amount/", $text, $matches)) {
			$duedate = getNormalizedDate($matches[1], TRUE);
			$reminddate = strftime("%Y-%m-%d, 9:00 AM", strtotime($duedate) - (7 * 86400));
			return array($duedate, $reminddate);
		}
	}
}

Now that I know which vendor the bill is from, what the bill date is, and what the due date is, I copy the file to a directory that’s mirrored to Nextcloud, and trigger an AppleScript that creates a reminder in the Reminders app:

system("cp $thisfile $bills_root/$billtype/$newfilename");

$reminder_script = <<<EOT
tell application "Reminders"
	set myList to list "Bills"
  tell myList
  	make new reminder with properties { name:"$billtype Bill $billdate", due date:date "$duedate", remind me date:date "$reminddate", body: "file:///$bills_root/$billtype/$newfilename" }
  end tell
 end tell
EOT;

exec("/usr/bin/osascript -e '$reminder_script'");

By including a file:/// URL as the body of the reminder, with a link to the PDF, I get a clickable link to the PDF of the bill included with the node:

Screen shot of the Mac Reminders app showing a bill

Nextcloud fits into this system in two ways:

  1. The PDF files of the bills are mirrored to a “bills” directory in Nextcloud, where I can reference them as PDF files. They’re named by the vendor and the date for ease of reference.
  2. My Reminders app on my Mac is connected to the Tasks app in Nextcloud. This not only serves to back up the reminders, but also allows me access to the reminders from the Tasks app on my Android phone. This is all powered by CalDAV, which is an open standard.

As such, I’m leveraging the utility of desktop and mobile applications without being beholden to them, and I’m maintaining control over where my data is stored and in what format.

I’ve been using this new system since August, and it’s proved a capable replacement for Evernote; indeed, because more of the workflow is automated, it’s actually a better system than I had in Evernote, and I enjoy using it more.

I’ve still got more Evernote-functionality-replacing to work on, mostly its utility as a place to dump various bits and bobs of information, from boarding pass to network diagrams, that I don’t want to lose.

But replacing Evernote’s bills payment function in my life is a huge hurdle overcome.

Comments