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!