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
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
};
- s = msg_parse_flags (h, s [↩]

