I began consuming mail with Emacs and Notmuch when I was but a young boy… it was the year 2018… a simpler time, to be sure.
Setting all of this up was relatively simple in the beginning. (You can see what I mean by checking previous revisions of this file.) In the intervening years things like multifactor authentication have taken the world by storm and made it nigh impossible to use IMAP to sync lovely mail messages. Those were the days…
Possibly optional: I use Outlook at work, so if you’re not required to use MFA, you can probably skip this step.
Install DavMail. If you’re a Fedora user, you can install DavMail via COPR. Otherwise, choose from the following:
You’ll need to ”register an app” and copy the tenant and client IDs
and change the following in ~/.davmail.properties
:
davmail.oauth.clientId=yourappid
davmail.oauth.redirectUri=https://login.microsoftonline.com/common/oauth2/nativeclient
To sync mail to a local maildir, install isync
. You can use your
favorite package manager, but it’s worth noting that I ended up
building mbsync
from source to remedy some problem I can’t remember.
If you’re a macOS user, you’ll need to download and install a CA
bundle. mbsync
relies on a CA certificate bundle as its trust store
(as opposed to macOS using Keychain, which iSync doesn’t
support). Download and install a CA cert bundle:
wget https://curl.haxx.se/download/curl-7.57.0.tar.gz
tar xvf curl-7.57.0.tar.gz
cd curl-7.57.0/lib
./mk-ca-bundle.pl
mv ca-bundle.crt ~
Now we can tell mbsync
how to behave via ~/.mbsyncrc
:
# [Global]
# Create missing mailboxes on producer/consumer.
Create Both
# Equivalent of --full on command line
Sync All
# Tells mbsync to save its state to consumer mailbox.
SyncState *
# Necessary to avoid duplicate files.
Expunge Both
# [IMAP Settings]
# Define an account
IMAPAccount my_cool_account_name
# The following Host/Port values are correct for DavMail.
# You'll need to change them if you're using IMAP with GMail or whatever.
Host localhost
Port 1143
User you@example.com
# This doesn't matter if you're using DavMail.
# Otherwise, it would be wise to use GPG instead of
# storing your password in plain text.
PassCmd "echo foo"
SSLType None
AuthMechs LOGIN
# You may need to point to a different cert bundle if not using Fedora.
# If you're using macOS, use the path to the cert bundle created above.
CertificateFile /etc/ssl/certs/ca-bundle.crt
# Set IMAP timeout to avoid timeout while syncing big files.
Timeout 300
# [Stores]
# Define remote store and tell it to use account defined
# above.
IMAPStore my_cool_account_name-remote
Account my_cool_account_name
# Define local store to sync with remote.
MaildirStore my_cool_account_name-local
# Not sure - docs recommend it.
Subfolders Verbatim
# Path to local store.
Path ~/mail/
# Tell mbsync where to put INBOX.
Inbox ~/mail/inbox
# [Channels]
# Define work-inbox channel; remote as producer, local as consumer; syncs
# INBOX.
Channel my_cool_account_name-inbox
Master :my_cool_account_name-remote:
Slave :my_cool_account_name-local:
Patterns "INBOX"
# As above but sync "Sent Items"
# Repeat this for every folder you'd like to sync
Channel my_cool_account_name-sent
Master :my_cool_account_name-remote:"Sent"
Slave :my_cool_account_name-local:"Sent Items"
Patterns "Sent"
# [Groups]
# Finally, group all channels together
Group my_cool_account_name
Channel my_cool_account_name-inbox
Channel my_cool_account_name-sent
Running mbsync -a
should now work.
NOTE: If you’re having trouble, try running mbsync
with -v
(with more
“v”s to increase verbosity). Some problems may be related to the
Patterns
directive above. To troubleshoot, try listing remote IMAP
folders using the following:
openssl s_client -connect imap.example.com:993 -crlf
See RFC3501 section 6.1 for IMAP commands.
Install notmuch
via your favorite package manager.
Now, run notmuch setup
and follow the prompts. Afterwards, run notmuch
new
to populate the notmuch database.
Now we need something to send mail.
Install msmtp
using your favorite package manager.
Create ~/.msmtprc
and add the following:
# Set default values for all following accounts.
defaults
# If you're using DavMail, set auth to "plain" and tls to "off".
auth on
tls on
# Use the path from above or the CA cert bundle created earlier if you're using macOS.
tls_trust_file /Users/<name>/ca-bundle.crt
logfile ~/.msmtp.log
account <pick_a_name>
# If you're using something other than DavMail set the following accordingly.
host localhost
port 1025
from <name>@example.com
user <name>
# If you're using DavMail, the following doesn't matter.
passwordeval "echo `gpg -q --for-your-eyes-only --no-tty -d ~/.passwd/minort.gpg`"
# Set a default account
account default : work
Test your configuration with the following:
$ msmtp --account=<account> -Sd
I use afew for automatic mail tagging/sorting. My configuration file is huge and probably needs updating, but I’ll provide a simplified version below.
# This is the default filter chain
[KillThreadsFilter]
[ArchiveSentMailsFilter]
[ListMailsFilter]
# [Filter.]
# message =
# query =
# tags =
# You can use regex capture groups for tag assignment using HeaderMatchingFilter.
# This is an example of a Nagios filter:
[HeaderMatchingFilter.1]
header = Subject
pattern = [*]{2}\s(?P<type>[A-Z]+)\s--\sProject:\s(?P<project>[a-z1-9]+),\s(?P<host>[^/]+)/.*\sis\s(?P<state>\b[A-Z]+\b)
tags = +{type};+{project};+{host};+{state};+nagios;+updates;-new
[Filter.1]
message = A cool message
query = 'subject:"/your kewl/"' AND from:bestfriend@example.com
tags = +best-friend;-new
[InboxFilter]
[MailMover]
folders = Inbox "Sent Items" Junk
rename = True
# This rule moves mail tagged with "junk" to a Junk folder.
Inbox = 'tag:junk':Junk
I sync mail on a remote server that’s always connected to the internet and sync mail to my desktop using muchsync.
I use the following script to sync mail using a systemd
timer:
#! /bin/bash
vpn_status=$(nmcli connection show vpn | \
grep -i vpn.vpn-state | \
awk '{print $5}')
if [[ $vpn_status =~ "connected" ]]; then
/usr/local/bin/muchsync -v example.com -v
/usr/bin/emacsclient -e '(tm/notmuch-notify "3mins")'
fi
exit 0
Here’s the function definition for tm/notmuch-notify
:
(defun tm/notmuch-notify (time-range)
"Generate desktop notifcations for new mail received in TIME-RANGE.
This function utilizes `notmuch-call-notmuch-sexp' to fetch the
latest messages tagged inbox and send a notification to the
desktop. TIME-RANGE should be the beginning of an Xapian date
range. For example, an input of \"20mins\" translates to
\"date:20mins..\"."
(let* ((latest-messages
(apply #'notmuch-call-notmuch-sexp `("search"
"--format=sexp"
"--format-version=4"
"--sort=newest-first"
"tag:inbox"
,(format "date:%s.." time-range))))
(who)
(when)
(what)
(mail-message)
(body))
(mapcar (lambda (mail-message)
(setq when (plist-get mail-message :date_relative))
(setq who (if (string-match-p "|"
(plist-get mail-message :authors))
(progn (string-match "[[:space:],]\\{0,2\\}\\([a-zA-z[:space:]]+\\)|"
(plist-get mail-message
:authors))
(match-string 1 (plist-get mail-message
:authors)))
(plist-get mail-message :authors)))
(setq what (plist-get mail-message :subject))
(setq body (format "<b>%s</b>\n<b>%s</b>\n\n%s" when who what))
(async-start
`(lambda ()
(require 'notifications)
(notifications-notify :title "New message(s)!\n"
:body ,body
:app-name "notmuchmail"))
'ignore))
latest-messages)))
Put this in your init.el
:
(use-package notmuch
:config
(setq message-send-mail-function 'async-smtpmail-send-it
;; If you use Fedora (or CentOS), it's likely that the
;; alternatives program takes care of this for you.
sendmail-program "/usr/local/bin/msmtp"
user-mail-address "me@example.com"
smtpmail-smtp-user "me@example.com"
;; Choose the appropriate value based on your mail provider;
;; the following is correct for Davmail running locally.
smtpmail-smtp-service 1025
smtpmail-smtp-server "localhost"
;; If you use multiple mail accounts, you'll need the
;; following.
message-sendmail-extra-arguments '("--read-envelope-from")))
That should be enough to do the very basics.
See M-x customize-group notmuch
for more customization options.
The following is old and I haven’t used it in a couple of years. It may or may not work but I’ll keep it for posterity.
I’ve searched for ways to do this and found many suggestions.
I happened upon this post on a notmuch list and this github project.
I attempted to get notspam
to work but had no luck. Instead of
struggling to get it to behave, I instead wrote a Python script
to do it (found in .scripts
).
I decided to go with bogofilter
for spam filtering.
After much gnashing of teeth, I deduced the following
information regarding installation:
First, you’ll need to install the correct version of Berkeley-DB; brew installs Berkeley DB version 6 as a dependency, but I’m not sure it actually works.
When running bogofilter -s < /dev/null
, I got an
error that read:
(null) Can't open file 'wordlist.db' in directory '/Users/tminor/.bogofilter'. error #22 - Invalid argument. Make sure that the database version this program is linked against can handle the format of the data base file (after updates in particular).
I found information in several places insinuating that only Berkeley DB version 4 is supported (bogofilter last appears to have been updated in 2013).
So. Install Berkeley DB version 4:
$ brew install berkeley-db@4
Then download bogofilter’s latest source files:
$ wget https://downloads.sourceforge.net/project/bogofilter/bogofilter-1.2.4/bogofilter-1.2.4.tar.bz2 $ tar -vxjf bogofilter-1.2.4.tar.bz2 $ cd bogofilter-1.2.4
Follow the INSTALL
instructions. (I didn’t have to make any changes;
it Just Worked [TM].)
You should get a return about the number of messages trained.
To train bogofilter, I used the following steps:
In GMail, search for “label:promotions”, and apply a new filter; in
my case, I added a label called “SpamTraining”. When mbsync
grabs mail,
it will create a new folder by that name under the maildir.
Tag all mail in that directory with notmuch
:
$ notmuch tag +spam -- path:"gmail/SpamTraining/cur"
Now, use notmuch
to train bogofilter
:
# some spam $ notmuch search --output=files tag:spam | xargs bogofilter -svB # and some ham $ notmuch search --output=files NOT tag:spam NOT path:gmail/SpamTraining/new | xargs bogofilter -nvB
After bogofilter
has been trained, feel free to remove the spam samples
from your machine.
Alternatively, you can export the mail using Google Takeout, which you can find here. You’ll have to follow the above steps and select the label as the desired target for downloading.
This is probably a good strategy for downloading samples for future
training in case you need to retrain. I’m guessing the same procedure
could be followed for training ham, but you’d have to figure out
how to find ham and apply labels in GMail first. I found that
-{label:promotions and label:social}
worked reasonably well as
a ham search query, but it still captured some stuff I didn’t care
to have in my inbox.