Organizing my invoices with Reminders

One of the least pleasant parts of my job is following up on invoices that haven’t been paid. Because I don’t have a secretary, I have to remember to periodically check on my outstanding bills, and that’s something I’ve never been especially good at. There’s always something more interesting to do. This year, I decided to automate the remembering. I can’t say my new system is fully formed yet, but it’s in good enough shape to blog about.

My business is somewhat unusual in that I seldom work directly with the entity that’s going to pay me. Furthermore, my bills have to be vetted by the people I do work with directly before being sent along for payment. This can slow things down, so although the terms on my invoices are “net 30,” it usually takes more than 30 days to get paid.

This creates a delicate issue: when should I first follow up on an unpaid bill? Sending out a late notice at 30 days is unrealistic, but waiting another 30-day cycle is too long. I tend to split the difference and send out my first followup at about 45 days.

But an automated system for following up at 45 days is dumb. Too often the 45th day is on a weekend, when I don’t want to send out notices and my clients can’t act on them anyway. A better approach for automating followups is to schedule them in terms of weeks. That way, unpaid invoices get followed up on the same weekday they were sent out. I’ve decided to start the reminder process 49 days after the invoice is sent out.

For simplicity, and because it means I’ll still get the reminders on my phone if I’m not at a computer, I’ve decided to use the Reminders app. I’ve set up a list called Invoices within the app to schedule notifications to follow up on every invoice I send out.

And to automate the process of creating the reminders, I’ve added a couple of new bits to my invoicing script. As you can see at the link, my invoices are PDFs, and I send them out by email using a script. The script extracts the relevant information from the invoice and generates a “cover letter” in the subject line and body of the email that goes along with the attached PDF. Currently, the script is set up to use MailMate, but it used to use Apple Mail.1

Here’s the new invoicing script:

 1:  #!/usr/bin/python
 3:  import os
 4:  import os.path
 5:  import sys
 6:  import applescript
 7:  import subprocess
 9:  # Templates for the subject and body of the message.
10:  sTemplate = '{0}: Drang invoice {1}'
11:  bTemplate = '''Attached is Drang Engineering invoice {0} for {1} covering\
12:   recent services on the above-referenced project. Payment is due {2}.
14:  Thank you for using Drang. Please call if you have any questions or need\
15:   further information.
17:  Regards,
18:  Dr. Drang
20:  '''
22:  # AppleScript template for getting project contact info.
23:  cScript = '''
24:  tell application "Contacts"
25:    set contact to item 1 of (every person whose name contains "{}")
26:    return value of item 1 of (emails of contact)
27:  end tell
28:  '''
30:  # AppleScript template for setting a reminder.
31:  rScript = '''
32:  set rem to "Invoice {} on {}"
33:  set rmdate to (current date) + 49*days
34:  tell application "Reminders"
35:    tell list "Invoices"
36:      set minder to (make new reminder with properties {{name:rem, remind me date:rmdate}})
37:    end tell
38:    activate
39:    show minder
40:  end tell
41:  '''
43:  # Establish the home directory for later paths.
44:  home = os.environ['HOME']
46:  # Open the project list file and read it into a string.
47:  pl = open("{}/Dropbox/pl".format(home)).readlines()
49:  # Get the selected invoice PDF names from the command line.
50:  pdfs = sys.argv[1:]
52:  # Make a new mail message for each invoice.
53:  for f in pdfs:
54:    f = os.path.abspath(f)
56:    # Use pdftotext from the xpdf project ( to extract
57:    # the text from the PDF as a list of lines.
58:    invText = subprocess.check_output(['/usr/local/bin/pdftotext', '-layout', f, '-'])
60:    # Pluck out the project name, project number, invoice number, invoice amount,
61:    # and due date.
62:    for line in invText.split('\n'):
63:      if 'Project:' in line:
64:        parts = line.split(':')
65:        name = parts[1].split('  ')[0].strip()
66:        invoice = parts[2].lstrip()
67:      if 'project number:' in line:
68:        number = line.split(':')[1].split()[0].lstrip()
69:      if 'Invoice Total:' in line:
70:        parts = line.split(':')
71:        amount = parts[1].split()[0].strip()
72:        due = parts[2].lstrip()
74:    # Get the email address of the client.
75:    try:
76:      client = [x.split('|')[2] for x in pl if number in x.split('|')[1]][0]
77:      email = applescript.asrun(cScript.format(client)).strip()
78:    except:
79:      client = ''
80:      email = ''
81:    addr = "{0} <{1}>".format(client, email)
83:    # Construct the subject and body.
84:    subject = sTemplate.format(name, invoice)
85:    body = bTemplate.format(invoice, amount, due)
87:    # Create a MailMate message with the subject, body, and attachment.
88:    cmd = ['emate', 'mailto', '-t', addr, '-s', subject, f]
89:    proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
90:    proc.stdin.write(body)
92:    # Add a reminder to the Invoices list.
93:    applescript.asrun(rScript.format(invoice, name))

The new parts are Lines 30–41, which are a template for the AppleScript that creates the new reminder, and Lines 92–93, which fill in the template and run the script.

The key to creating a new reminder is to understand the properties of a reminder, which we can see by opening up the Reminders library:

Reminder properties

As you can see in Line 36, the only properties I bother with are the name and the too-cute-by-half remind me date. When the script is finished running, I have both an email ready to send and a reminder to follow up in seven weeks if it isn’t paid.

Reminders details

You’ll note that I have my invoice reminders set up to repeat every two weeks, so if a bill doesn’t get paid, I get a reminder to follow up at 49 days, 63 days, 77 days, 91 days, etc. Unfortunately, the repeating properties of a reminder aren’t available to AppleScript, so I have to add that part by hand. It’s easy enough to do, but it really should be scriptable. After all, Calendar has the recurrence property for events—Reminders could have exactly the same thing if Apple’s left hand knew what its right hand was doing.2

Every time I send out a reminder email,3 I click the Completed button and the reminder’s date resets to two weeks in the future. When payment arrives, I delete the reminder for that invoice.

As I said above, this is probably not in its final form, but it’s working reasonably well so far.

  1. And will do so again. I’ve been testing out Apple Mail and MailHub on my MacBook Air for a few months, and I find it to be a great combination. I just need to get around to setting it up on my iMac at work. ↩︎

  2. What’s that? OmniFocus/Thing/SomethingElse has a really cool way to set up this kind of repeating reminder? I’m happy to hear that, but I have no intention of starting up with a new task manager. ↩︎

  3. Yes, there’s a script for that, too; it’s a slight rewrite of the invoicing script, but without the Reminders stuff. ↩︎