From: [email protected]
Date: 2018-08-26
Subject: Email as an Application Interface

I've long wondered how an application that uses email as its inter-
face might be built.  I got all the clues I needed after setting up
Postfix and Mailman on my VPS.

The magic is in the alias data Postfix uses to map email recipients
to users on the destination system.  Allow me to disclaim that this
is  very  ad-hoc  and  I'm  sure  there are better ways to do this.
Think of this as a prototype.

There are four pieces to this:

1. A Postfix alias for my program.
2. A program to parse an inbound email and take an appropriate action.
3. A program to send out an expense report when requested.
4. A configuration for msmtp which sends out the email.

The implicit rule for any incoming email looks like this:

    dave: dave

This is an alias that tells  Postfix  to  deliver  emails  sent  to
[email protected] to the user dave on the local system.  I have out-
of-the-box aliases set up in my /etc/aliases file:

    # /etc/aliases
    mailer-daemon: postmaster
    postmaster: root
    nobody: root
    -snip-

When setting up Mailman, I noticed aliases that looked like this:

    list-subscribe: "|/var/lib/Mailman/bin/subscribe"

The file at /var/lib/Mailman/bin/subscribe is a binary  executable.
This  alias  pipes the content of the inbound email to this program
for processing.  That's all there is to it.  It's just a pipe.  How
Unixy!

Now  I  just  need  to  create a program that will parse an inbound
email and do something with it.  I  track  the  expenses  that  are
shared among the members of my household.  At the end of the month,
I calculate the difference and settle up.  I do this  by  pocketing
receipts  when I make purchases and entering the amount into a text
file along with the date and payee.  It would be nice to be able to
do  this by sending in an email with the pertinent information.  It
would also be nice if I could request a report of all the  expenses
recorded so far including a total.

The alias for Postfix looks like this:

    expense: "|awk -f /usr/lib/expense/expense.awk -vdate=$(date +'%Y-%m-%d')"

This tells Postfix to pipe the inbound email message to awk, speci-
fies my expense.awk script, and passes in a  variable  called  date
set  to the current date.  Here are the permission settings for the
script:

    $ls -l /usr/lib/expense

    -rw-r--r-- 1 root root 578 Aug 26 16:01 expense.awk

And here is the script itself:

    BEGIN {
      boundary_seen = 0
      boundary = "^$"
      line = ""
    }
    (boundary_seen == 1) && ($0 !~ /^$/) {
      line = $0
      exit
    }
    /^From:/ {
      $1 = ""; from = $0
      sub(/^.*</,"",from)
      sub(/>.*$/,"",from)
    }
    /^Content-Type/ {
      boundary = "^Content-Type: text/plain;"
    }
    $0 ~ boundary {
      boundary_seen = 1
    }
    END {
      if (line ~ /^[0-9]/) {
        split(line,values)
        amt = values[1]
        payee = values[2]
        printf "%s %s %s %s0, date, amt, payee, from >> "/home/dave/expense"
      }
      if (line ~ "[Tt]otal") {
        system ("awk -f/usr/lib/expense/total.awk /home/dave/expense |msmtp " from)
      }
    }

This script parses out the relevant line from the inbound email and
determines  whether  it's  an  expense that's being submitted or if
it's a request for an expense report.  This is  a  bit  complicated
because,  depending  on the mail client, the inbound message may or
may not have a multipart attachment.  Mutt produces a normal  email
message  with  the body separated from the headers by a blank line.
The mobile Gmail client, however, is compelled to provide  an  HTML
version of even the simplest message.  The sequence of the patterns
and actions is important, too.   We  don't  want  to  test  (bound-
ary_seen  ==  1)  until the line after we see the boundary.  That's
why this test comes first.

In the case that the email body looks like this:

    44.23 Whole Foods

expense.awk will create the following line in /home/dave/expense:

    2018-08-26 44.23 Whole Foods [email protected]

After adding a few expense items to the list, I may want to  get  a
report  of  all  the  items  in the list along with a total.  To do
this, I send an email to [email protected] with the word  "Total"
in the body.  The expense.awk script will shell out and run the to-
tal.awk script and pipe the output to msmtp.  Here's the  total.awk
script:

    BEGIN {
      total = 0
    }
    { total += $2; print }
    END {
      print "Total: " total
    }

Simple, right?  This could almost be a one-liner.

The  msmtp  configuration  gave  me the most trouble.  Initially, I
just copied my ~/.msmtprc to /usr/lib/expense/ and  specified  this
config for msmtp with the C option.  I could get everything to work
when running it from the console, but I  got  a  permissions  error
whenever  I ran the request through Postfix.  It turns out that the
right(er) thing to do is to create a system-wide  msmtp  config  in
/etc/msmtprc.   I  copied  my  personal config to this location and
things started working just fine.

With this pattern established, there are a several application pat-
terns that can be applied.

* Log expenses, calories, life events, to-dos
* Gateway to gopher, dict, the web, twtxt, RSS
* Form delivery and processing
* Email as CMS front end

Happy hacking!