Invoice email generator redux

Here’s the reason I wanted to develop the Python applescript library I posted yesterday: a command-line script for automatically generating invoicing emails. The email includes

The motivation and overall plan of the script is described in this post from 2011, when I used the appscript module to extract information from Address Book and compose the message in Mail. None of the basic ideas of the script have changed; I’ve just changed how I do the AppleScripty interprocess communication.

Here’s the script, called invoice, which I keep in ~/bin/:

python:
 1:  #!/usr/bin/python
 2:  
 3:  import os
 4:  import os.path
 5:  import sys
 6:  from applescript import asrun
 7:  from subprocess import check_output
 8:  
 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}.
13:  
14:  Thank you for using Drang. Please call if you have any questions or need\
15:   further information.
16:  
17:  --
18:  Dr. Drang
19:  leancrew.com
20:  
21:  '''
22:  
23:  # AppleScript template for getting project contact info.
24:  cScript = '''
25:  tell application "Contacts"
26:    set contact to item 1 of (every person whose name contains "{}")
27:    return value of item 1 of (emails of contact)
28:  end tell
29:  '''
30:  
31:  # AppleScript template for composing email.
32:  mScript = '''
33:    tell application "Mail"
34:      activate
35:      set newMsg to make new outgoing message with properties {{subject:"{0}", content:"{1}", visible:true}}
36:      tell the content of newMsg
37:        make new attachment with properties {{file name:"{4}"}} at after last paragraph
38:      end tell
39:      tell newMsg
40:        make new to recipient at end of to recipients with properties {{name:"{2}", address:"{3}"}}
41:      end tell
42:    end tell
43:  '''
44:  
45:  # Establish the home directory for later paths.
46:  home = os.environ['HOME']
47:  
48:  # Open the project list file and read it into a string.
49:  pl = open("{}/Dropbox/pl".format(home)).readlines()
50:  
51:  # Get the selected invoice PDF names from the command line.
52:  pdfs = sys.argv[1:]
53:  
54:  # Make a new mail message for each invoice.
55:  for f in pdfs:
56:    f = os.path.abspath(f)
57:  
58:    # Use pdftotext from the xpdf project (http://foolabs.com/xpdf) to extract
59:    # the text from the PDF as a list of lines.
60:    invText = check_output(['/usr/local/bin/pdftotext', '-layout', f, '-'])
61:  
62:    # Pluck out the project name, project number, invoice number, invoice amount,
63:    # and due date.
64:    for line in invText.split('\n'):
65:      if 'Project:' in line:
66:        parts = line.split(':')
67:        name = parts[1].split('  ')[0].strip()
68:        invoice = parts[2].lstrip()
69:      if 'project number:' in line:
70:        number = line.split(':')[1].split()[0].lstrip()
71:      if 'Invoice Total:' in line:
72:        parts = line.split(':')
73:        amount = parts[1].split()[0].strip()
74:        due = parts[2].lstrip()
75:  
76:    # Get the email address of the client.
77:    try:
78:      client = [x.split('|')[2] for x in pl if number in x.split('|')[1]][0]
79:  
80:      email = asrun(cScript.format(client))
81:    except:
82:      client = ''
83:      email = ''
84:  
85:    # Construct the subject and body.
86:    subject = sTemplate.format(name, invoice)
87:    body = bTemplate.format(invoice, amount, due)
88:  
89:    # Create a mail message with the subject, body, and attachment.
90:    asrun(mScript.format(subject, body, client, email, f))

From the command line, it would be run like this:

invoice inv3456.pdf

where inv3456.pdf is the invoice I want to send out.1 This is what gets generated:

Invoice email sample

I don’t have the email sent immediately, because I may want to cc: someone else or add more information to the body.

The fundamental difference between this script and the original is in Lines 23-43, which contain templates for the source code of AppleScripts that communicate with the Contacts and Mail applications. The templates get filled and run in Lines 80 (for the Contacts script) and 90 (for the Mail script).

Although this is more verbose than the original script—brevity is not the soul of AppleScript—I think it’s easier to read than a script with lines like

message.content.paragraphs[-1].after.make(new=k.attachment, with_properties={k.file_name: pdf})

I also have this set up as a Service called Make Invoice Emails.

Make Invoice Emails Service

The Service does nothing more than call the command-line script. It’s convenient, though, because I can run it by right-clicking on the invoice file in the Finder and choosing Make Invoice Emails from the Services submenu. No opening a Terminal window, no typing.


  1. Created through FileMaker by a system that dates back to 1995. But that’s a different story. 


One Response to “Invoice email generator redux”

  1. has says:

    Academic now, but:

    make new attachment \
                with properties {file name: pdfPath} \
                at after last paragraph of content of newMsg
    
    mail.make(new=k.attachment, 
              with_properties={k.file_name: pdfPath},
              at=newMsg.content.paragraphs[-1].after)
    

    With a little practice, you can write AppleScript and appscript code that looks almost identical in form. The only differences are those already inherent in AppleScript vs Python syntax due to Python insisting on totally precise punctuation while AS lets you futz it. And while that may make Python code less pretty, it also makes it way more resistant to the sort of ambiguity and confusion that AS is notorious for (plus it won’t screw up your careful manual linewraps just for the lulz either).