Quick invoicing emails with MailMate

I’m not ready to write a post on the positives and negatives (mostly positives) of MailMate, but I am ready to share a script that automates one of my common emailing tasks.

The invoices I send to clients are PDFs that I attach to a short email. The body of the email is boilerplate: here’s invoice #xxx for $yyy on project zzz; payment is due on ddd. It’s not hard to write such an email from scratch, nor is it hard to use a TextExpander snippet with fill-in fields for each variable. But I’m too lazy for either of those. Because the invoice itself contains all the variable values, I wrote a script that extracted the necessary info from the PDF and created an email, with attachment, ready to send to the client.

Actually, I wrote that script twice: first using Hamish Sanderson’s (now deprecated) appscript module for Python, and then using my own applescript module. Both of those scripts built the email in Apple’s Mail application, which I stopped using when it stopped working, so I had to rewrite it again for MailMate. Here it is, still named invoice, and still invoked from the command line with one or more invoice PDF files as arguments.

 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:  # Establish the home directory for later paths.
31:  home = os.environ['HOME']
33:  # Open the project list file and read it into a string.
34:  pl = open("{}/Dropbox/pl".format(home)).readlines()
36:  # Get the selected invoice PDF names from the command line.
37:  pdfs = sys.argv[1:]
39:  # Make a new mail message for each invoice.
40:  for f in pdfs:
41:    f = os.path.abspath(f)
43:    # Use pdftotext from the xpdf project (http://foolabs.com/xpdf) to extract
44:    # the text from the PDF as a list of lines.
45:    invText = subprocess.check_output(['/usr/local/bin/pdftotext', '-layout', f, '-'])
47:    # Pluck out the project name, project number, invoice number, invoice amount,
48:    # and due date.
49:    for line in invText.split('\n'):
50:      if 'Project:' in line:
51:        parts = line.split(':')
52:        name = parts[1].split('  ')[0].strip()
53:        invoice = parts[2].lstrip()
54:      if 'project number:' in line:
55:        number = line.split(':')[1].split()[0].lstrip()
56:      if 'Invoice Total:' in line:
57:        parts = line.split(':')
58:        amount = parts[1].split()[0].strip()
59:        due = parts[2].lstrip()
61:    # Get the email address of the client.
62:    try:
63:      client = [x.split('|')[2] for x in pl if number in x.split('|')[1]][0]
64:      email = applescript.asrun(cScript.format(client)).strip()
65:    except:
66:      client = ''
67:      email = ''
68:    addr = "{0} <{1}>".format(client, email)
70:    # Construct the subject and body.
71:    subject = sTemplate.format(name, invoice)
72:    body = bTemplate.format(invoice, amount, due)
74:    # Create a MailMate message with the subject, body, and attachment.
75:    cmd = ['emate', 'mailto', '-t', addr, '-s', subject, f]
76:    proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
77:    proc.stdin.write(body)

I don’t want to repeat myself here. Lines 43–59 use Derek Noonburg’s pdftotext utility to dig into the invoice PDF and extract the project name, project number, invoice number, invoice amount, and due date. That process was explained in the first post about this script. Similarly, Lines 61–68 pull the client name from a plain text file named pl as explained in the first post, and then get the email address from the Contacts app as explained in the second.

The new stuff is at the very end of the script: Lines 74–77. It uses the emate command line utility that’s packaged with MailMate.1 First, I made a symbolic link to the utility in my $PATH:

ln -s /Applications/MailMate.app/Contents/Resources/emate ~/Dropbox/bin/emate

This wasn’t strictly necessary, but it makes it easier to call emate, both from within programs and interactively from the command line.

You build a message through emate by passing the body of the message to it through standard input, specifying the header fields through command line switches, and specifying attachments through arguments. Here’s an example:

echo "How could this happen?" | emate mailto -t "tcook@apple.com" -s "Mail in Mavericks"  hulksmash.gif

Line 75 builds the list of all the parts of the emate command, Line 76 uses the subprocess module to create a process of the command, and Line 77 sends the body of the message to the command via standard input. When the command runs, MailMate opens with a new mail message, ready to be edited or (more commonly) sent as is.

Invoice email in MailMate

Obviously, you can’t use this script directly, because the emails you send regularly have different inputs. But if you’re a MailMate user, take a look at what emate can do. As with all automation, you can speed up your workflow and eliminate the errors that creep in when you do everything by hand.

  1. That’s emate, not eMate