Figure 1: Notmuch Emacs

Figure 1: Notmuch Emacs

Davmail

In my work routines, Microsoft is somewhat unavoidable, and trying to have an email client that can sync Exchange in linux has always been a pain to me. This is my documentation on how to use notmuch, davmail, and msmtp to sync my work emails.

Configuration

davmail.server=true
davmail.mode=Auto
davmail.url=https://mail.domain/EWS/Exchange.asmx
davmail.defaultDomain=DOMAIN
davmail.ssl.nosecurecaldav=false
davmail.ssl.nosecureimap=false
davmail.ssl.nosecureldap=false
davmail.ssl.nosecurepop=false
davmail.ssl.nosecuresmtp=false
davmail.caldavPort=1081
davmail.imapPort=1144
davmail.ldapPort=1390
davmail.popPort=1111
davmail.smtpPort=1026
davmail.imapAutoExpunge=true
davmail.allowRemote=false
davmail.logFilePath=/tmp/davmail-domain.log
davmail.logFileSize=1MB
davmail.disableGuiNotifications=true
davmail.disableTrayActivitySwitch=true
davmail.showStartupBanner=false
davmail.enableKerberos=false

Notes

  • davmail.mode set to auto.
  • uses port > 1024 to avoid using root privileges.
  • Also set the davmail.logFilePath to a path that is accessible.
  • davmail.server set to true since I don’t need the GUI.
  • All the ssl options is not necessary since it will only be accessed locally.

msmtp

It’s a simple smtp client with fairly complete sendmail compatibility. This is not a full tutorial on how to setup msmtp, there’s already much information for that online1.

# Set default values for all following accounts.
defaults
auth           on
tls            on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile        /tmp/msmtp.log

account exchange
host localhost
port 1025
tls off
tls_starttls off
auth plain
user DOMAIN\username
from username@domain
passwordeval pass domain/username

Notes

  • logfile should be accessible (read/write) by the user.
  • account must be unique if there’s more than one accounts.
  • tls options should refer to the exchange server.
  • port is defined in davmail.

offlineimap

[general]
accounts = domain
maxsyncaccounts = 4
socktimeout = 10
pythonfile = ~/.local/bin/offlineimap-helper.py

[Account domain]
localrepository = domain-local
remoterepository = domain-remote

[Repository domain-local]
Type = Maildir
localfolders = ~/.mail/domain
sync_deletes = yes
autorefresh = 0.5
quick = 10
keepalive = 240
holdconnectionopen = yes

[Repository domain-remote]
Type = IMAP
remotehosteval = get_credentials("domain", "host")
remoteporteval = get_credentials("domain", "port")
remoteusereval = get_credentials("domain", "user")
remotepasseval = get_credentials("domain", "passeval")
maxconnections = 1
ssl = no
retrycount = 4
tls_level = tls_no_ssl

Notes

  • We’ll be using a helper script to determine the host, port, user, and password.

offlineimap-helper.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/usr/bin/env python
"""Offlineimap helper script"""

from subprocess import check_output
from os import getenv, path
import json

JSON_FILE = "credentials.json"
if getenv("XDG_DATA_HOME") and path.exists(getenv("XDG_DATA_HOME") + "/" + "offlineimap"):
    JSON_PATH = getenv("XDG_DATA_HOME") + "/" + "offlineimap"
else:
    JSON_PATH = getenv("HOME") + "/" + ".local/share/offlineimap"


f = open(JSON_PATH + "/" + JSON_FILE)
creds_data = json.load(f)

def get_credentials(name, query):
    for item in creds_data["accounts"]:
        if item["name"] == name:
            if query == "host":
                return item["host"]
            elif query == "port":
                return item["port"]
            elif query == "user":
                return item["user"]
            elif query == "passeval":
                return item["passeval"]
  • Notes

    • This script will read a json file located at ~/.local/share/offlineimap/credentials.json

~/.local/share/offlineimap/credentials.json

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
    "accounts": [
        {
            "name": "domain",
            "host": "localhost",
            "port": 1144,
            "user": "username",
            "passeval": "somepassword"
        }

    ]
}

Notes:

  • use a strict permission (such as 0700) for this file since password is written in clear text.

notmuch

[database]
path=/home/kristian.alexander/.mail

[user]
name=Kristian Alexander P
primary_email=username@domain
[new]
tags=new

[search]

exclude_tags=deleted;spam
[maildir]
synchronize_flags=true

Notes:

  • This file should be generated by the command notmuch setup.

notmuch hooks

We’ll be using two hooks, one is pre-new, which will be sourced whenever notmuch is running. And the other one is post-new, which we’ll be sourced after all new emails is synced.

1
2
#!/usr/bin/env/bash
[[ -x $(which offlineimap) ]] && offlineimap -s

This will ensure each time we call notmuch new, it will run offlineimap.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#!/usr/bin/env bash

newcount=$(notmuch count tag:new)
summary="Notmuch: ${newcount} new message"

if [ $newcount -gt 1 ]; then summary="${summary}s"; fi
if [ $newcount -gt 0 ]; then detail="$(notmuch search --output=summary --format=json tag:new | sed -e 's/.*authors": "//;s/|[^"]*"/"/;s/", "subject": "/ : /;s/".*//')"; fi

notmuch tag -new        -- tag:new

# See the notmuch pre-hook for DISPLAY thoughts
# Desktop notifications
if [ $newcount -gt 0 ]; then logger -t notmuch "calling notify-send '$summary' '$detail'" && notify-send -i /usr/share/icons/Papirus/symbolic/actions/mail-message-new-symbolic.svg "$summary" "$detail"; fi
exit 0

cron

There’s lot of ways to automate the syncing process. systemd-timer is one of them, but since I’m an old-school person, I prefer cron.

1
crontab -l
1
2
3
MAILTO=alexforsale@yahoo.com
MAILFROM=alexforsale@yahoo.com
*/45 * * * *    notmuch new

Other

With all the processes above, we could use other mail clients like thunderbird or geary.