Email with emacs

 
about me     cv     research     blog    

Gmail web aplicaction is generally very good with many features. However I felt I was strugling with its usability - need to interact with a mouse in many places, browser spell checker, fast notificaction of a new email, multiple account managment and lack of emacs way of writing the text. In the process I tried Geary, which was good but was sometimes crashing, Nylas1, which had instant email notifications but became buggy and now is not activelly developed. I felt that my enmails are still hard to manage and write.

With getting to know emacs better I was pushed in its keybord bynding, beutifull way of extending functionaity which both made writing efficient and distraction free. I looked up how email works on emacs and came accross mu4e with which now I wokrk with 3 accounts. I can write, search and read emails very fast and have no need for interent connection for that. That sometimes is convinietn for example to look the booking of hostel or flight.

To get my almost perfect setup I struggled a lot. I had to fight with a bug which corrupted index database; I had to develop a way for having multiple accounts while not having them all at one pot; Because emails were stored loccaly I had to develop cron job to update them and account for network connection faillures; Learn to store them under encrypted folder; Dealing with emacs failures due to mu4e plugin running in the background; Theese are many problems which had owerhelmed me since last Oktober when I started to take email client in emacs serioslly. Fortunatelly now it had worked flawleslly for last two months and I fell that I have solved frustration with usability I had.

Syncing email accounts

Before to downlaoad a email we need a safe place to store it. That can be easally done with Ubuntu where you simply need to execute a following commands 1

sudo apt-get install ecryptfs-utils
ecrypt-setup-private

and follow instructions in terminal. Afterwards loging into your user account will unlock encrypted folder ~/.Private into ~/Private and everything will work as a normal folder.

To actually sync emails I use mbsync, which is installed with

sudo apt-get install isysnc

This command asks for a configuration file which for my gmail account looks as follows

  IMAPStore master
Host imap.gmail.com
User akels14@gmail.com
Pass XXXXXXX
UseIMAPS yes
RequireSSL yes
CertificateFile /etc/ssl/certs/ca-certificates.crt

MaildirStore slave
Path ~/Private/Email/akels14/

Channel inbox
Master ":master:INBOX"
Slave ":slave:inbox"
Create Both
Expunge Both
SyncState *

Channel sent
Master ":master:[Gmail]/Sent Mail"
Slave ":slave:sent"
Create Both
Expunge Both
SyncState *

for the fileds which had been written go to man page of mbsync (man mbsync). I name this file mbsync-ak.conf and then synchronizes all my email into ~/Private/Email/akels14 folder with a command (since pasword is written in plaintext I also put that in my Private folder)

mbsync -c ~/Private/.config/mbsync-ak.conf inbox sent

The first run takes about an hour for me but then it is done. One warning must be given Since mbsync does synchronize emails between master and slave it can also delete emails at master if file is deleted locally (slave). Fortunatllly all emails deleted except from trash directory can be found in Archive folder. That saved my ass a couple of times at the begining when I tried to make sense of mbsync settings.

The synchronization to be usefull needs to work all the time (but some may preffer to do it ondemand) which I accomplish with cronjob. To start writing commands in crontab execute "crontab -e" and write a following lines in it

PATH=/opt/someApp/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
* * * * * flock -n /tmp/mbsync-ak.lock timeout 300 mbsync -c /home/janiserdmanis/Private/.config//mbsync-ak.conf inbox sent

The path variable is neat workaround for avoiding to write full paths of aplicactions - flock, timeout, mbsync. This script will synchronize email every minute and if network connection fails or if large files needs to downloaded cron will wait (flock) for full synchronization for 300 secoonds (timeout) until the command will be executed again. And that is about all needed to have synchronization at every minute to be set up and addingf multiple accounts follows the same pattern.

Reading and sending emails

To read synchronized emals we will need emacs and mu4e installed by

sudo apt-get install mu emacs

Now to run mu4e we create an init.el file with a following contents

(require 'mu4e')
(setq mu4e-maildir (expand-file-name "~/Private/Email/akels14"))

and run that with a simple command

emacs --load init-simple.el --eval "(mu4e)"

where command after –eval, which can be put at the end of init.el, is executed after initfile init.el is loaded. To see new emails you have to index them which is done with keybinding "U" (next time would be instantenous) which updates by default db under "~/.mu".

The last step to have a functional email client is configure smptp which is responsable for sending emails. That can be done in the init.el by adding a following lines

(require 'smtpmail)
(setq user-full-name  "Janis Erdmanis")
(setq mu4e-user-mail-address-list '("akels14@gmail.com" "XXXX@gmail.com"))
(setq user-mail-address "akels14@gmail.com")
(setq mu4e-reply-to-address "akels14@gmail.com")

One adress list so mu4e know your mail adresses, one for mu4e to know which adress to use for composing and last adress to be given to teh recepient so he is pushed to reply to a given adress, which is fairly good spot for your protected spam-free email adress.

Then when I move in the navigation and find email I want to reply I press "R". With the configuration so far presented I would be asked for my account details to send mail with smtp which would be stored in plaintext in "~/.authinfo". The contents of the file is as follows

machine smtp.gmail.com login graphitewriter port 587 password xxxxxxx
machine smtp.gmail.com login akels14 port 587 password xxxxxxxx

where corresponding line to your email adreess used for sending will is picked up automatically. One warning must be emphasized do not store paswords in plaintext, to avoid that move I move ".authinfo" file to the Private folder and symlink that with a command

ln -s ~/Private/.config/authinfo ~/.authinfo

Similarly one can move ~/.mu to the Private folder for a complete security.

So far this is most basic and usefull startingn point to start use emacs for mail reading and sending. I think one can pick up this setup and modify depending on his needs and put some snipets in init.el which can be found in google as I usually do. Multiple account managment and desktop integration is most obvious next steps which I will continue to reveal.

Multiple account management

To simply manage multiple accounts one can start emacs with a different init.el files by adding/modyfing a following lines

(setq mu4e-maildir (expand-file-name "~/Private/Email/akels14"))
(setq mu4e-mu-home (expand-file-name "~/Private/.mu/akels14"))

where the last one is needed to have seperate index for each maildir. That should work flawlessly as long as you would be satisfied to change windows for each account.

I wanted to use multiple accounts in a single window with a keyboard shortcut, thus I wrote/modified some scripts on the interent to have a behavior I wanted. Firstly I put my accounts in variables (I still append this to init.el)

(setq mu4e-user-mail-address-list '("XXXXXX@gmail.com" "je11011@lu.lv" "akels14@gmail.com"))
(setq user-full-name  "Janis Erdmanis")

(defvar my-mu4e-account-alist
  `(("graphitewriter"
     (user-mail-address "XXXXX@gmail.com")
     (mu4e-reply-to-address "XXXXX@gmail.com")
     (mu4e-maildir ,(expand-file-name "~/Private/Email/graphitewriter"))
     (mu4e-mu-home ,(expand-file-name "~/Private/.mu/graphitewriter"))
     )
    ("je11011"
     (user-mail-address "je11011@lu.lv")
     (mu4e-reply-to-address "je11011@lu.lv")
     (mu4e-maildir ,(expand-file-name "~/Private/Email/je11011"))
     (mu4e-mu-home ,(expand-file-name "~/Private/.mu/je11011"))
     )
    ("akels14"
     (user-mail-address "akels14@gmail.com")
     (mu4e-reply-to-address "akels14@gmail.com")
     (mu4e-maildir ,(expand-file-name "~/Private/Email/akels14"))
     (mu4e-mu-home ,(expand-file-name "~/Private/.mu/akels14"))
     )))

Then to change account I simply have to execute a following function with a account name

(defun mu4e-set-account (account-name)
  (interactive)
  (mu4e~proc-kill)
  (let ((vars (cdr (assoc account-name my-mu4e-account-alist))))
    (if vars
        (mapc #'(lambda (var)
                  (set (car var) (cadr var)))
              vars)
      (error "No email account '%s' was found" account-name)))
  ;; For having fancy mode-line
  (setq mode-line-end-spaces
        (list (propertize " " 'display `(space :align-to (- right ,(length account-name))))
              account-name))
  ;; Also need to remove lock file which sometimes messes up
  (shell-command  (concat "rm -rf " mu4e-mu-home "/xapian/flintlock"))
  )

which is can be executed at any point in emacs for example with "M-x + mu4e-set-account + Enter + akels14".

By adding completition of account names and keybord shortcut ";" to change accounts I was satisfied with my setup:

(setq my-accounts nil)
(defun printlist (mylist)
  "Filling my-accounts with alist keys from mylist"
  (push (car (car mylist)) my-accounts)
  (if (not (eq (length mylist) 1)) (printlist (cdr mylist))))
(printlist my-mu4e-account-alist)

(defun mu4e-set-account-interactive ()
  "Changes the email account"
  (interactive)
  ;(mu4e-set-account (read-string "Enter the account name:"))
  (setq mu4e-account (ido-completing-read "Enter the account name: " my-accounts))
  (mu4e-set-account mu4e-account)
  )

(add-hook 'mu4e-main-mode-hook
          (lambda () (local-set-key (kbd ";") #'mu4e-set-account-interactive)))

This code part needs some reviewing, whch would make it shorter, as I use global variable and make function where I do not need. Nevertheless it works flawleslly.

Desktop integration

The last step was to make dekstop integration - to see instantenously that a new email had arrived. I wanted to have an icon and badge showing number of unread emails in Ubuntu. My aproach uses DBus for that purpose with which I make damon process and its ability to change *.desktop icons in panel. Thus first we need to make a .desktop icon for the emacs command.

A following init script is needed to let X-window system to know that seperate aplicaction is being started

APPNAME=mu4e
emacs --name $APPNAME -q --load  init-simple.el &
sleep 1 && xprop -name $APPNAME -f WM_CLASS 8s -set WM_CLASS "$APPNAME, $APPNAME"

This script I store in ~/bin/mu4e for which I add execution permissions with "chmod +x ~/bin/mu4e". A .desktop file is being stored in ~/.local/share/applications/mu4e.desktop which has a folloowing contents

#!/usr/bin/env xdg-open
[Desktop Entry]
Version=1.0
Type=Application
Terminal=false
Exec=mu4e
Name=Mu4e
Comment=Simple email client based on emacs
Icon=envelope
StartupWMClass=mu4e

where icon is stored in ~/.local/share/icons/envelope.png. Then this aplicaction can be found in Unity dash as mu4e.

The next step is adding a badge for this aplicaction in the taskbar to show email count. That I do with a following script

#!/usr/bin/python

ccommand = "ls ~/Private/Email/graphitewriter/inbox/new | wc -l"

from gi.repository import Unity, Gio, GObject, Dbusmenu

# The mu4e must be put into ~/.local/share/applicactions
launcher = Unity.LauncherEntry.get_for_desktop_id("mu4e.desktop")

launcher.set_property("count", 0)
launcher.set_property("count_visible", True)

### Defining the loop in right way

import commands
def update_mail_count():
    count = int(commands.getstatusoutput(ccommand)[1])
    launcher.set_property("count", count)
    launcher.set_property("count_visible", True)

    if count>0:
        launcher.set_property("urgent", True)
    else:
        launcher.set_property("urgent",False)
    
    return True

loop = GObject.MainLoop()
GObject.timeout_add_seconds(1, update_mail_count)
loop.run()

which is stored in ~/bin/mu4e-counter with chmod +x. When this script is started it will add badge icon showing number of files in ~/Private/Email/graphitewriter/inbox/new where unread emails are stored and will update for changes there each second. For convinience I start this script on login by adding it in "Startup Applicactions".

Fancy features and list of usefull keybindings

This barebones version is good, but needs some additional snippets for better use

(require 'mu4e-contrib)

;; For processing html emails
(setq mu4e-html2text-command 'mu4e-shr2text)
(setq shr-color-visible-luminance-min 80)

;; show images
(setq mu4e-show-images t)

;; Allows to view email in browser
(add-to-list 'mu4e-view-actions
'("ViewInBrowser" . mu4e-action-view-in-browser) t)

;; No need to showing email twice
(setq mu4e-headers-skip-duplicates t)

Usefull keybindings in the mu4e are as follows

a + V To view email in the browser
C To compose message
R To reply
F To forward
E To edit draft meessage
Ctrl-c + Ctrl-c To send message
A + w To oppen attachment with some applcation
A + e To oppen attachemnt with emacs
e To save attachment
o To oppen attachemt with default applicaction
; To change account
q To quit, go back
C-g To stop whatever
d + x To delete message
P Toogle between thread view
H for help
W Includes related messages in view
w On link, copies it to clipboard

Troubleshooting

Sometimes mu4e corrupts its indexed database. Then reindexing is being needed which can be accomplished by removing ~/.mu (or if multiple elsewhere) folder.

Refferences


This website was made with Skeleton with modificactions from JuliaDiff.