4 min read

Automatic mail sorting based on recipient

In this article I will describe how I set up my mail on the wonderful Uberspace 7 using .qmail and maildrop to automatically create subfolders in my inbox and sort mail in these based on the recipient (i.e. my) mail address.

The basic idea is that if you own an domain like yourdomain.tld you can add unlimited email addresses. So I had the idea (which is not new) to create different email adresses for different services like facebook@yourdomain.tld for Facebook, github@yourdomain.tld for GitHub, etc. You get the idea.
Creating a new mailbox for every service is way too much work. Catching all emails in one mailbox and creating a new folder and a filter to sort the mails into a folder every time is still really annoying. It's way easier if you can just register for a site and a new folder in your inbox gets created automatically when an email arrives on the domain for the new service.

So first I created my main mailbox which I use for my own mail. Emails to this address go directly into the inbox without filtering.

uberspace mail user add isabell

Then I added yourdomain.tld to my uberspace for use with email. Don't forget to set the corresponding DNS records on your provider (will be output by the add script).

uberspace mail domain add yourdomain.tld

On Uberspace the first sorting is done by .qmail files. By default all emails that are not caught by some specific .qmail file will match .qmail-default where it gets send to |/usr/bin/vdeliver (The pipe at the beginning tells qmail to pipe the mail into an external program, in this case vdeliver). More on this in the Uberspace Wiki. vdeliver then delivers the mail to the corresponding inbox if it exists and drops all others (actually they are dropped earlier, but lets keep it simple)
Because we want to receive all emails we forward all mails that arrive at .qmail-default directly to our inbox

echo "isabell@yourdomain.tld" > .qmail-default

Now all emails arrive at your inbox. So where's the filter? Now we get to maildrop. Instead of accepting all mails directly into our inbox we call maildrop first. How? We tell qmail to do it for us:

echo "|maildrop $HOME/.myfilter" > .qmail-isabell

$HOME/.myfilter contains our filter configuration. For more information on maildrop filters see the Uberspace Wiki.
Take a look at the filter first:

logfile "$HOME/mailfilter.log"

# set default Maildir to user isabell
MAILDIR="$HOME/users/isabell"

DESTDIR="$MAILDIR"

# Instead of looking at "To" or "Cc" we look at the
# "Delivered-To: " header, which is added by qmail, because
# it's easier and it's reliably there in contrast to the others

# For now just loop over all occurences and take the last email
ADDR=""
foreach /^Delivered-To:\s*.*/
{
    ADDR=getaddr($MATCH)
}

# Use sed to extract just the local part of the url without username
SERVICE=`echo $ADDR | sed 's/^[a-z0-9]*-\(.*\)@.*/\1/'`

# If it was delivered to a subdomain, put it in a subfolder, otherwise
# deliver it to the main inbox (also in case of an error)
if ( $SERVICE )
{
    DESTDIR="$MAILDIR/.INBOX.$SERVICE"
}

# check if the folder already exists and create and subscribe if necessary
if ( ! `test -d "$DESTDIR"` )
{
    `maildirmake "$DESTDIR"`
    `echo "INBOX.$SERVICE" >> "$MAILDIR"/subscriptions`
}

# last line, deliver to $MAILDIR
to "$DESTDIR"

I already added a lot of comments, so I think most commands should be clear, but here again a basic rundown. qmail adds a Delivered-To: header when it delivers a mail to an inbox. It does not do this if you call a programm in the .qmail file (i.e. |maildrop .myfilter, I learned this the hard way). This is why we first catch all mails in .qmail-default and forward it to our main user .qmail-isabell and THEN call maildrop to apply the filter, instead of just calling maildrop directly in the .qmail-default.
Why go to such lenghts? Because the To: and Cc: headers are not always there (think mailing list, bcc, ...) and there can be multiple recipients. The solution via Delivered-To: is easier since there may be multiple headers, but the first one (i.e. first added, listed last) will be the one where the other server initially sent our email and it will be the only email in this header. Thanks to Thorsten Köster for the idea.

With that in mind the script is easy. First define a default mailbox and set the destination mailbox to to be sure that no emails are dropped. Next go over all Delivered-To: headers, get the last one and extract the local part (i.e. <address> below) with sed. Remember the domain looks like this:

Delivered-To: <user>-<namespace>-<address>@<domain>.<tld>

If you have a namespace enabled you probably need to adjust not only the filter, but probably also the forwarding earlier.

So if we have a valid subdomain (i.e. it wasn't sent to isabell@yourdomain.tld directly) we check if there the directory in the inbox already exists, if not create it and subscribe to it. Lastly deliver the mail.
Two things to note: Subfolders in Mailbox format are created with leading dots and dots as seperators and not a "real" directory tree. Second, in IMAP you have to subscribe to a folder on a client to be able to see it. Usually you subscribe to all folders when you add the account to your client, but the oftentimes don't get updated automatically if new ones appear, therefore hiding the newly created folder. But because the list is stored on the server (so it get's synced across clients) we can easily modify it.

That's it! Lot's of explanation and not so much to do, but great results.