mutt and gmail

Per recommendation from a neckbeard friend, Aaron, I set out to try out Mutt as my email client. Since my email is hosted by Gmail, there’s a little extra configuration needed than just setting up an IMAP inbox. Also, since people actually send multimedia emails, I wrote a small patch for Mutt that detects it’s talking to a Gmail IMAP server and adds a couple custom headers to the message, one of which is the permalink to the email so it can be easily opened in a browser if need be. I’m sure I am one of the few that actually like Google Contacts, so I use Goobook for address completion. And no reason to go through all the trouble of setting up Mutt and not setup GPG for signing/encryption too. I am a fan of Ethan Schoonover’s Solarized color scheme, but I prefer a bit more contrast: I modified the Mutt colors Solarized Dark 16 colors for this preference.

Latest versions of my conf/patch can be found at:
mutt conf GitHub repo
mutt gmail patch GitHub repo

Mutt Higher Contrast Solarized Dark

If you’re new to Mutt, take a look at these links:
MuttWiki: MuttGuide
My first mutt

If you’re not a fan of Git or GitHub (why not?), the current bare minimum files as of the writing of this post are below. Everything should live in ~/.mutt

muttrc

set certificate_file= ~/.mutt/certificates
set header_cache    = ~/.mutt/cache/headers
set message_cachedir= ~/.mutt/cache/bodies
set mailcap_path    = ~/.mutt/mailcap

set editor          = "vim + -c 'set textwidth=72' -c 'set wrap' -c 'set nocp' -c 'set spell' -c '?^$'"
set query_command   = "goobook query '%s'"
bind editor <Tab> complete-query

set folder      = "imaps://imap.gmail.com:993"
set spoolfile   = "+INBOX"
set postponed   = "+[Gmail]/Drafts"

set imap_check_subscribed   = yes
set imap_idle               = yes
set imap_list_subscribed    = yes
set ssl_force_tls           = yes

unset beep
unset collapse_unread
set implicit_autoview   = yes
set mail_check          = 30
unset markers
unset mark_old
set pager_index_lines   = 10
set pager_context       = 5
set pager_stop          = yes
set pipe_decode         = yes
set quit                = ask-yes
unset record
set reverse_alias       = yes
set sort                = threads
set sort_aux            = reverse-last-date-received
unset sort_re
set thorough_search
set thread_received     = yes
set tilde               = yes
set timeout             = 5
set uncollapse_jump     = yes

alternative_order text/plain text/enriched text/html 

set attribution     = "On %d, %n <%a> wrote:"
set status_format   = "-%r-Mutt: %f [Msgs:%?M?%M/?%m%?n? New:%n?%?o? Old:%o?%?d? Del:%d?%?F? Flag:%F?%?t? Tag:%t?%?p? Post:%p?%?b? Inc:%b? %?l? %l?]---(%s/%S)-default-%>-(%P)---"
set index_format    = "%4C %Z  %[%a %b %D %Y %l:%M:%S%p]  %-15.15L (%?l?%4l&%4c?) %s"

unset copy
set edit_headers    = yes
set fast_reply      = yes
set forward_format  = "Fwd: %s"
set forward_quote   = yes
set include         = yes
unset recall
unset reply_self
set reply_to        = yes

ignore *
unignore date: from: to: cc: subject:
unignore x-mailing-list: posted-to:
unignore x-mailer:
unignore x-gm-permalink:
hdr_order x-gm-permalink: date: from: to: cc: subject:

source ~/.mutt/identity
source ~/.mutt/colors
source ~/.mutt/keybindings

source ~/.mutt/gpg-agent.rc
set crypt_autosign              = yes
set crypt_replysign             = yes
set crypt_replysignencrypted    = yes
set pgp_use_gpg_agent           = yes

set sidebar_visible = yes
set sidebar_width    = 30
set sidebar_delim   = '|'
color   sidebar_new brightmagenta   default

identity

set realname    = "MY NAME"
set from        = "example@example.org"
set imap_user   = "example@gmail.com"
set imap_pass   = "PASSWORD"
set smtp_pass   = "PASSWORD"
set smtp_url    = "smtps://example@gmail.com@mtp.gmail.com:465/"
set pgp_sign_as = "0xFFFFFFFF"
# vim: ft=muttrc

keybindings

# Gmail style keybindings
bind  editor <space> noop
bind  index,pager c  mail       #Compose
macro index,pager e  "<save-message>=[Gmail]/All Mail<enter><enter>" "Archive conversation"
bind  generic     x  tag-entry      #Select Conversation
bind  index       x  tag-thread     #Select Conversation
bind  pager       x  tag-message    #Select Conversation
bind  index,pager s  flag-message   #Star a message
macro index,pager +  <save-message>=[Gmail]/Important<enter><enter> "Mark as important"
macro index,pager !  <save-message>=[Gmail]/Spam<enter><enter> "Report spam"
bind  index,pager a  group-reply    #Reply all
bind  index,pager \# delete-thread  #Delete
bind  index,pager l  copy-message   #Label
bind  index,pager v  save-message   #Move to
macro index,pager I  <set-flag>O    "Mark as read"
macro index,pager U  <clear-flag>O  "Mark as unread"
macro index,pager ga "<change-folder>=[Gmail]/All Mail<enter>"  "Go to all mail"
macro index,pager gs <change-folder>=[Gmail]/Starred<enter> "Go to 'Starred'"
macro index,pager gd <change-folder>=[Gmail]/Drafts<enter>  "Go to 'Drafts'"
macro index,pager gl <change-folder>?               "Go to 'Label'"
macro index,pager gi <change-folder>=INBOX<enter>       "Go to inbox"
macro index,pager gt "<change-folder>=[Gmail]/Sent Mail<enter>" "Go to 'Sent Mail'"
folder-hook +INBOX 'macro index,pager y "<save-message>=[Gmail]/All Mail<enter><enter>" "Archive conversation"'
folder-hook +[Gmail]/Trash macro index,pager y <save-message>=INBOX<enter><enter> "Move to inbox"
folder-hook +[Gmail]/Starred bind  index,pager y flag-message #"Toggle star"

# Other bindings
#macro index \CR "T~N\n;WNT~O;WO\CT~A" "mark all messages read"
macro index \CR "T~N\n;WN;T~O\n;WO;\CT~A\n" "mark all messages read"

# Sidebar bindings
bind index,pager \CJ sidebar-prev
bind index,pager \CK sidebar-next
bind index,pager \CO sidebar-open

folder-hook . "exec collapse-all"

gpg-agent.rc

# -*-muttrc-*-
#
# Command formats for gpg-agent
#

set pgp_decode_command="gpg --no-verbose --quiet --batch --output - %f"
set pgp_verify_command="gpg --no-verbose --quiet --batch --output - --verify %s %f"
set pgp_decrypt_command="gpg --no-verbose --quiet --batch --output - %f"
set pgp_sign_command="gpg --no-verbose --batch --quiet --output - --armor --detach-sign --textmode %?a?-u %a? %f"
set pgp_clearsign_command="gpg --no-verbose --batch --quiet --output - --armor --textmode --clearsign %?a?-u %a? %f"
set pgp_encrypt_only_command="pgpewrap gpg --batch --quiet --no-verbose --output - --encrypt --textmode --armor --always-trust -- -r %r -- %f"
set pgp_encrypt_sign_command="pgpewrap gpg --batch --quiet --no-verbose --textmode --output - --encrypt --sign %?a?-u %a? --armor --always-trust -- -r %r -- %f"
set pgp_import_command="gpg --no-verbose --import %f"
set pgp_export_command="gpg --no-verbose --export --armor %r"
set pgp_verify_key_command="gpg --verbose --batch --fingerprint --check-sigs %r"
set pgp_list_pubring_command="gpg --no-verbose --batch --quiet --with-colons --list-keys %r"
set pgp_list_secring_command="gpg --no-verbose --batch --quiet --with-colons --list-secret-keys %r"
set pgp_good_sign="`gettext -d gnupg -s 'Good signature from "' | tr -d '"'`"

colors

# Modified solarized dark
#
# Original: https://github.com/altercation/mutt-colors-solarized/blob/master/mutt-colors-solarized-dark-16.muttrc
# By: Ethan Schoonover <es@ethanschoonover.com>
#
#
# make sure that you are using mutt linked against slang, not ncurses, or
# suffer the consequences of weird color issues. use "mutt -v" to check this.

# basic colors ---------------------------------------------------------
color normal        default         defaul
color error         red             defaul
color tilde         black           defaul
color message       cyan            defaul
color markers       red             white
color attachment    white           defaul
color search        brightmagenta   defaul
color status        brightyellow    black
color indicator     brightblack     yellow
color tree          yellow          default                                     # arrow in threads

# basic monocolor screen
mono  bold          bold
mono  underline     underline
mono  indicator     reverse
mono  error         bold

# index ----------------------------------------------------------------

#color index         red             default         "~A"                        # all messages
color index         brightred       default         "~E"                        # expired messages
color index         blue            default         "~N"                        # new messages
color index         blue            default         "~O"                        # old messages
color index         brightmagenta   default         "~Q"                        # messages that have been replied to
#color index         brightgreen     default         "~R"                        # read messages
color index         blue            default         "~U"                        # unread messages
color index         blue            default         "~U~$"                      # unread, unreferenced messages
color index         brightyellow    default         "~v"                        # messages part of a collapsed thread
color index         brightyellow    default         "~P"                        # messages from me
color index         cyan            default         "~p!~F"                     # messages to me
color index         cyan            default         "~N~p!~F"                   # new messages to me
color index         cyan            default         "~U~p!~F"                   # unread messages to me
#color index         brightgreen     default         "~R~p!~F"                   # messages to me
color index         red             default         "~F"                        # flagged messages
color index         red             default         "~F~p"                      # flagged messages to me
color index         red             default         "~N~F"                      # new flagged messages
color index         red             default         "~N~F~p"                    # new flagged messages to me
color index         red             default         "~U~F~p"                    # new flagged messages to me
color index         black           red             "~D"                        # deleted messages
color index         brightcyan      default         "~v~(!~N)"                  # collapsed thread with no unread
color index         yellow          default         "~v~(~N)"                   # collapsed thread with some unread
color index         green           default         "~N~v~(~N)"                 # collapsed thread with unread paren
# statusbg used to indicated flagged when foreground color shows other status
# for collapsed thread
color index         red             black           "~v~(~F)!~N"                # collapsed thread with flagged, no unread
color index         yellow          black           "~v~(~F~N)"                 # collapsed thread with some unread & flagged
color index         green           black           "~N~v~(~F~N)"               # collapsed thread with unread parent & flagged
color index         green           black           "~N~v~(~F)"                 # collapsed thread with unread parent, no unread inside, but some flagged
color index         cyan            black           "~v~(~p)"                   # collapsed thread with unread parent, no unread inside, some to me directly
color index         yellow          red             "~v~(~D)"                   # thread with deleted (doesn't differentiate between all or partial)
color index         yellow          default         "~(~N)"                    # messages in threads with some unread
color index         green           default         "~S"                       # superseded messages
color index         red             default         "~T"                       # tagged messages
color index         brightred       red             "~="                       # duplicated messages

# message headers ------------------------------------------------------

color hdrdefault    default         defaul
color header        green           default         "^(From)"
color header        blue            default         "^(To)"
color header        cyan            default         "^(Subject)"

# body -----------------------------------------------------------------

color quoted        blue            defaul
color quoted1       cyan            defaul
color quoted2       yellow          defaul
color quoted3       red             defaul
color quoted4       brightred       defaul

color signature     green            defaul
color bold          black           defaul
color underline     black           defaul
#
color body          brightcyan      default         "[;:][-o][)/(|]"    # emoticons
color body          brightcyan      default         "[;:][)(|]"         # emoticons
color body          brightcyan      default         "[*]?((N)?ACK|CU|LOL|SCNR|BRB|BTW|CWYL|
                                                     |FWIW|vbg|GD&R|HTH|HTHBE|IMHO|IMNSHO|
                                                     |IRL|RTFM|ROTFL|ROFL|YMMV)[*]?"
color body          brightcyan      default         "[ ][*][^*]*[*][ ]?" # more emoticon?
color body          brightcyan      default         "[ ]?[*][^*]*[*][ ]" # more emoticon?

## pgp

color body          red             default         "(BAD signature)"
color body          cyan            default         "(Good signature)"
color body          brightblack     default         "^gpg: Good signature .*"
color body          brightyellow    default         "^gpg: "
color body          brightyellow    red             "^gpg: BAD signature from.*"
mono  body          bold                            "^gpg: Good signature"
mono  body          bold                            "^gpg: BAD signature from.*"

# yes, an insance URL regex
color body          red             default         "([a-z][a-z0-9+-]*://(((([a-z0-9_.!~*'();:&=+$,-]|%[0-9a-f][0-9a-f])*@)?((([a-z0-9]([a-z0-9-]*[a-z0-9])?)\\.)*([a-z]([a-z0-9-]*[a-z0-9])?)\\.?|[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)(:[0-9]+)?)|([a-z0-9_.!~*'()$,;:@&=+-]|%[0-9a-f][0-9a-f])+)(/([a-z0-9_.!~*'():@&=+$,-]|%[0-9a-f][0-9a-f])*(;([a-z0-9_.!~*'():@&=+$,-]|%[0-9a-f][0-9a-f])*)*(/([a-z0-9_.!~*'():@&=+$,-]|%[0-9a-f][0-9a-f])*(;([a-z0-9_.!~*'():@&=+$,-]|%[0-9a-f][0-9a-f])*)*)*)?(\\?([a-z0-9_.!~*'();/?:@&=+$,-]|%[0-9a-f][0-9a-f])*)?(#([a-z0-9_.!~*'();/?:@&=+$,-]|%[0-9a-f][0-9a-f])*)?|(www|ftp)\\.(([a-z0-9]([a-z0-9-]*[a-z0-9])?)\\.)*([a-z]([a-z0-9-]*[a-z0-9])?)\\.?(:[0-9]+)?(/([-a-z0-9_.!~*'():@&=+$,]|%[0-9a-f][0-9a-f])*(;([-a-z0-9_.!~*'():@&=+$,]|%[0-9a-f][0-9a-f])*)*(/([-a-z0-9_.!~*'():@&=+$,]|%[0-9a-f][0-9a-f])*(;([-a-z0-9_.!~*'():@&=+$,]|%[0-9a-f][0-9a-f])*)*)*)?(\\?([-a-z0-9_.!~*'();/?:@&=+$,]|%[0-9a-f][0-9a-f])*)?(#([-a-z0-9_.!~*'();/?:@&=+$,]|%[0-9a-f][0-9a-f])*)?)[^].,:;!)? \t\r\n<>\"]"
# and a heavy handed email regex
color body          magenta         default         "((@(([0-9a-z-]+\\.)*[0-9a-z-]+\\.?|#[0-9]+|\\[[0-9]?[0-9]?[0-9]\\.[0-9]?[0-9]?[0-9]\\.[0-9]?[0-9]?[0-9]\\.[0-9]?[0-9]?[0-9]\\]),)*@(([0-9a-z-]+\\.)*[0-9a-z-]+\\.?|#[0-9]+|\\[[0-9]?[0-9]?[0-9]\\.[0-9]?[0-9]?[0-9]\\.[0-9]?[0-9]?[0-9]\\.[0-9]?[0-9]?[0-9]\\]):)?[0-9a-z_.+%$-]+@(([0-9a-z-]+\\.)*[0-9a-z-]+\\.?|#[0-9]+|\\[[0-2]?[0-9]?[0-9]\\.[0-2]?[0-9]?[0-9]\\.[0-2]?[0-9]?[0-9]\\.[0-2]?[0-9]?[0-9]\\])"

# *bold*
color body          blue            default         "(^|[[:space:][:punct:]])\\*[^*]+\\*([[:space:][:punct:]]|$)"
mono  body          bold                            "(^|[[:space:][:punct:]])\\*[^*]+\\*([[:space:][:punct:]]|$)"
# _underline_
color body          blue            default         "(^|[[:space:][:punct:]])_[^_]+_([[:space:][:punct:]]|$)"
mono  body          underline                       "(^|[[:space:][:punct:]])_[^_]+_([[:space:][:punct:]]|$)"
# /italic/  (Sometimes gets directory names)
color body         blue            default         "(^|[[:space:][:punct:]])/[^/]+/([[:space:][:punct:]]|$)"
mono body          underline                       "(^|[[:space:][:punct:]])/[^/]+/([[:space:][:punct:]]|$)"

# Border lines.
color body          blue            default         "( *[-+=#*~_]){6,}"

# vim: filetype=muttrc

mutt gmail patch

--- imap/command.c.orig	2012-03-07 22:59:11.784404517 -0800
+++ imap/command.c	2012-03-07 22:59:25.517273315 -0800
@@ -66,6 +66,7 @@
   "LOGINDISABLED",
   "IDLE",
   "SASL-IR",
+  "X-GM-EXT-1",
 
   NULL
 };
--- imap/message.c.orig	2010-08-24 09:34:21.000000000 -0700
+++ imap/message.c	2012-03-07 22:59:25.517273315 -0800
@@ -242,8 +242,10 @@
       char *cmd;
 
       fetchlast = msgend + 1;
-      safe_asprintf (&cmd, "FETCH %d:%d (UID FLAGS INTERNALDATE RFC822.SIZE %s)",
-                     msgno + 1, fetchlast, hdrreq);
+      safe_asprintf (&cmd, "FETCH %d:%d (%sUID FLAGS INTERNALDATE RFC822.SIZE %s)",
+                     msgno + 1, fetchlast,
+                     mutt_bit_isset (idata->capabilities, X_GM_EXT_1) ? "X-GM-MSGID " : "",
+                     hdrreq);
       imap_cmd_start (idata, cmd);
       FREE (&cmd);
     }
@@ -452,6 +454,12 @@
     }
   }
 
+  if (mutt_bit_isset (idata->capabilities, X_GM_EXT_1))
+  {
+    fprintf (msg->fp, "X-Gm-Msgid:%llu\n", HEADER_DATA(h)->msgid);
+    fprintf (msg->fp, "X-Gm-Permalink: https://mail.google.com/mail/#all/%llx\n", HEADER_DATA(h)->msgid);
+  }
+
   /* mark this header as currently inactive so the command handler won't
    * also try to update it. HACK until all this code can be moved into the
    * command handler */
@@ -1142,6 +1152,14 @@
       if1 == NULL)
         return -1;
     }
+    else if (ascii_strncasecmp ("X-GM-MSGID", s, 10) == 0)
+    {
+      s += 10;
+      SKIPWS (s);
+      h->data->msgid = strtoull (s, NULL, 10);
+
+      s = imap_next_word (s);
+    }
     else if (ascii_strncasecmp ("UID", s, 3) == 0)
     {
       s += 3;
--- imap/message.h.orig	2012-03-07 22:59:00.101513364 -0800
+++ imap/message.h	2012-03-07 22:59:29.586234445 -0800
@@ -37,6 +37,7 @@
   unsigned int parsed : 1;
 
   unsigned int uid;	/* 32-bit Message UID */
+  unsigned long long msgid;
   LIST *keywords;
 } IMAP_HEADER_DATA;
 
--- imap/imap_private.h.orig	2012-03-07 22:58:29.465804586 -0800
+++ imap/imap_private.h	2012-03-07 22:59:29.586234445 -0800
@@ -114,6 +114,7 @@
   LOGINDISABLED,		/*           LOGINDISABLED */
   IDLE,                         /* RFC 2177: IDLE */
   SASL_IR,                      /* SASL initial response draft */
+  X_GM_EXT_1,       /* Gmail IMAP Extensions */
 
   CAPMAX
 };
  1. s = msg_parse_flags (h, s []
  • http://www.techmansworld.com/ Michael Hazell

    I would like to tell you about a visual programming app called Illumination Software Creator.

    It can build apps for python, which I hear is very popular with the Disqus team.

    Check out this site below
    radicalbreeze.com

  • Ajith Antony

    The Gmail permalink patch is a great idea!  I’ll see if I can do something similar for Exchange.  It doesn’t appear that the Outlook Web Access permalinks use readily accessible data from the mail header.  (or if it does it is not plainly obvious)

    • http://www.dctrwatson.com John Watson

      I would check if Exchange has any IMAP extensions like Google. My patch requests a GMail specific header: X-GM-MSGID which is what is used in the permalink thankfully.

      • Ajith Antony

        Yeah, perhaps there is something useful that is not well documented.  The imap capabilities don’t allude to anything cool:


            [2012-06-13 19:50:48] 4 a0000 CAPABILITY 
        [2012-06-13 19:50:48] 4< * CAPABILITY IMAP4 IMAP4rev1 AUTH=NTLM AUTH=GSSAPI AUTH=PLAIN UIDPLUS CHILDREN IDLE NAMESPACE LITERAL+

  • http://www.techmansworld.com/ Michael Hazell

    I am just now starting to use Outlook.com as my email service. I upgraded to it from Hotmail, and i think it works awesome. I think there will be an email war at the current Outlook and Gmail. Outlook looks really good.

  • Kim

    How do I put up an avatar