# HG changeset patch # User Steve Losh # Date 1348983100 14400 # Node ID bd15116835e2a80a250eb85dafce935ec0db736a # Parent fe3eb7796d611b38fb17e2ebb7746d503e8c9067 Add the Mutt entry. diff -r fe3eb7796d61 -r bd15116835e2 .ffignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.ffignore Sun Sep 30 01:31:40 2012 -0400 @@ -0,0 +1,1 @@ +deploy diff -r fe3eb7796d61 -r bd15116835e2 content/blog/2012/10/the-homely-mutt.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/content/blog/2012/10/the-homely-mutt.html Sun Sep 30 01:31:40 2012 -0400 @@ -0,0 +1,1319 @@ + {% extends "_post.html" %} + + {% hyde + title: "The Homely Mutt" + snip: "Sparrow's dead? Why not try Mutt?" + created: 2012-10-01 10:00:00 + flattr: true + %} + +{% block article %} + +Now that [Sparrow][] is [effectively dead][sparrow-dead] many of its users will +be looking for a new email client. If you're not afraid of the terminal you may +want to give [Mutt][] a try. + +Mutt certainly isn't the prettiest email client around, and its +setup/configuration process is one of the ugliest out there. But once you get +it set up it's got a lot of advantages over many other email clients. + +In this post I'll show you how to set up Mutt on OS X like I do. + +[Sparrow]: http://sparrowmailapp.com/ +[sparrow-dead]: http://www.theverge.com/2012/7/20/3172222/google-buys-sparrow-mail +[Mutt]: http://www.mutt.org/ + +[TOC] + +How I Use Email +--------------- + +This setup is going to be specific to the way I work with email. Notably: + +* I have a Google Apps account that provides my steve@stevelosh.com email + address. +* I have many other email addresses, but they all simply forward to my main one. +* All mail I send comes from steve@stevelosh.com. +* I store my contacts in the OS X address book. +* All email comes into my inbox (or to a folder for a specific mailing list). +* Once I'm done with an email, I remove it from my inbox and it lives in the + "All Mail" archive. I don't sort email into folders after it arrives. +* I sometimes read email offline and mark it for deletion, then sync that + deletion back to the server once I get online again. +* Sometimes I write email without an internet connection and send it once I get + connected again. + +My email setup is tailored around those requirements, so that's what it does +best. Mutt is very configurable though, so if you work differently you can +probably bend it to make it do what you want. + +In particular, extending this setup to work with multiple email accounts +wouldn't be too much trouble. I used to work with two separate accounts until +I said "screw it, I'll just use the one". + +Other Guides and Resources +-------------------------- + +I've used a lot of other guides to figure out how to get this giant Rube +Goldberg machine of an email client working. Here are a few of them: + +* +* +* +* +* +* +* +* +* +* + +Overview +-------- + +I'm going to give it to you straight: getting this whole contraption set up is +going to take at least an hour from start to finish, not counting the time it'll +take to download all of your email and install stuff. Set aside an evening if +you're serious about this. + +It's an investment, and you might not want to make it. If not, go use +Thunderbird, er, Sparrow, er, I don't know, the Gmail web interface or +something. + +Mutt on its own doesn't do very much, so we're going to combine it with a few +other things to get the job done. Here's a bird's eye view of what it'll look +like when we're done: + +![Diagram](/media/images{{ parent_url }}/what-the-mutt.png) + +If this diagram doesn't make you run screaming you might just be masochistic +enough to make it through the initial setup of Mutt. If you do, you'll be +rewarded with email bliss that won't go away when Google or Facebook decide to +toss some money around. + +Getting Email +------------- + +First thing's first: we're going to pull down our email from Gmail to our local +machine. All of it. It'll take a while the first time you sync, but has a few +benefits. + +### Why Local Email? + +Having a local copy of all of your email means you've always got access to it, +no matter where you are. Looking for that one person's address they emailed you +six years ago when you're trying to find their house and you don't have an +internet connection? No problem, it's on your hard drive. + +This also acts as a backup in case Google ever decides to kill your Gmail +account. It'll be stored in a common format that a lot of programs can read, so +you've got a safety net. And the email is stored as normal files, so if you use +something like Time Machine or [Backblaze][] that's yet another free backup. + +In this setup **all of your email is stored as plain text**. If you want it +encrypted just use OS X's full-disk encryption and you're set. + +I use [offlineimap][] to pull email down from Gmail and get it on my hard drive. +Offlineimap will also sync any changes you make to this local copy back up to +Gmail. + +[offlineimap]: http://offlineimap.org/ +[Backblaze]: http://www.backblaze.com/partner/af3574 + +### The Alternative + +You may not care as much about reading your email offline as I do. If you can +tolerate always needing an internet connection to read your mail, you can skip +this painful section and follow [this guide][roma] instead. + +You'll probably still find the other sections of this post interesting though. + +[roma]: http://empt1e.blogspot.com/2009/10/using-mutt-with-gmail-imap-complete.html + +### Installing offlineimap + +I've gone through a number of laptops in the past few years, and each time +I spend a painful half hour or so screwing around with the latest version of +offlineimap's backwards-incompatible changes. + +If you're determined to run the latest version of offlineimap, you can install +it with pip or something. If you just want to download your fucking email and +get on with your life, you can follow the instructions I've laid out for you +here: + + :::text + git clone git://github.com/spaetz/offlineimap.git + cd offlineimap + git checkout 679c491c56c981961e18aa43b31955900491d7a3 + python setup.py install + +That's the version I'm using. It works. You can use a newer one if you want, +but expect to spend some time figuring out how to fix the configuration in this +post to work with whatever breaking changes have been made since then. The last +time I tried this I got to rewrite all my nametrans stuff. That was fun. + +### Configuring offlineimap + +Once you've got offlineimap installed, you'll need to create +a `~/.offlineimaprc` file. You can keep it in your dotfiles repo and symlink it +into place if you want. Here's a sample to get you started: + + :::text + [general] + ui = TTY.TTYUI + accounts = SteveLosh + pythonfile=~/.mutt/offlineimap.py + fsync = False + + [Account SteveLosh] + localrepository = SteveLosh-Local + remoterepository = SteveLosh-Remote + status_backend = sqlite + postsynchook = notmuch new + + [Repository SteveLosh-Local] + type = Maildir + localfolders = ~/.mail/steve-stevelosh.com + nametrans = lambda folder: {'drafts': '[Gmail]/Drafts', + 'sent': '[Gmail]/Sent Mail', + 'flagged': '[Gmail]/Starred', + 'trash': '[Gmail]/Trash', + 'archive': '[Gmail]/All Mail', + }.get(folder, folder) + + [Repository SteveLosh-Remote] + maxconnections = 1 + type = Gmail + remoteuser = steve@stevelosh.com + remotepasseval = get_keychain_pass(account="steve@stevelosh.com", server="imap.gmail.com") + realdelete = no + nametrans = lambda folder: {'[Gmail]/Drafts': 'drafts', + '[Gmail]/Sent Mail': 'sent', + '[Gmail]/Starred': 'flagged', + '[Gmail]/Trash': 'trash', + '[Gmail]/All Mail': 'archive', + }.get(folder, folder) + folderfilter = lambda folder: folder not in ['[Gmail]/Trash', + 'Nagios', + 'Django', + 'Flask', + '[Gmail]/Important', + '[Gmail]/Spam', + ] + +It's kind of long, so let's go through it line by line and see what's going on. + + :::text + [general] + ui = TTY.TTYUI + accounts = SteveLosh + pythonfile=~/.mutt/offlineimap.py + fsync = False + +First we tell offlineimap to use the `TTY.TTYUI` ui. Yes, this program that +syncs your email has multiple user interfaces. I guess if you can't decide what +color the bikeshed should be you can just build a whole bunch of bikesheds +instead. + +Then we specify the accounts. There's only one because as I said before: +I only use a single email account that all my addresses forward to. If you +wanted to have many, you'd change this line. + +The `pythonfile` is just a file that offlineimap will parse (as Python) before +loading the rest of the config, so you can define custom helper functions more +easily. We'll see more of this later. + +We're also telling offlineimap that it doesn't need to fsync after every single +operation. This will speed things up, and since it's just a local copy it's +typically not a big deal if we lose an email here and there from a crash (it'll +just be synced the next time anyway). + + :::text + [Account SteveLosh] + localrepository = SteveLosh-Local + remoterepository = SteveLosh-Remote + status_backend = sqlite + +This next section hooks up a few things. First, it tells offlineimap which +local and remote repositories to use for the account. Manual configuration +instead of sane defaults is a recurring theme we'll see throughout this process. + +Hey, I titled the entry "The *Homely* Mutt" for a reason. + +We're also going to use a SQLite-based cache for this account. If you don't +already have SQLite you'll want to get it with `brew install sqlite`. + + :::text + [Repository SteveLosh-Local] + type = Maildir + localfolders = ~/.mail/steve-stevelosh.com + nametrans = lambda folder: {'drafts': '[Gmail]/Drafts', + 'sent': '[Gmail]/Sent Mail', + 'flagged': '[Gmail]/Starred', + 'trash': '[Gmail]/Trash', + 'archive': '[Gmail]/All Mail', + }.get(folder, folder) + +Now we're getting to the meat of the configuration. This "local repository" is +going to be the mail as it sits on our hard drive. We're going to use the +[Maildir format][maildir] because it plays nicely with Mutt (and tons of other +stuff). + +Then we specify the path where we're going to keep the mail. This is going to +take a lot of space if you've got a lot of mail. Attachments are downloaded +too. When I said you're getting an offline copy of all your email I meant *all* +of it. + +I think offlineimap needs the `~/.mail` directory created for it. It's been +a while since I did this, so I might be wrong, but if it complains about not +being able to access the mail folders just go ahead and `mkdir ~/.mail`. + +Next we have the craziest part of the configuration: name translation. + +Here's the issue: offlineimap needs to know how to translate the names of +folders on the IMAP server to folder names on your hard drive. + +Also, Gmail doesn't actually use *folders* but its own concept called "labels". +But since the IMAP protocol doesn't know about labels, it fakes them by making +them appear to be folders. + +User-created labels in Gmail (like "Mercurial" or "Clients") will appear as +folders with those names through IMAP. + +Built-in, special Gmail folders have names that start with `[Gmail]/`. We need +to turn those into something sane for our hard drive, so that's what this +nametrans setting is for. It's a Python function that takes the remote folder +name and returns the name that should be used on your local hard drive. + +Yes, you read that right. This is Python code embedded in the right hand side +of an INI file's setting assignment. I am not fucking with you, this is +seriously how you do it. Go ahead and crack open that beer now. + +So the "Sent Mail" folder in your Gmail account will be synced to +`~/.mail/steve-stevelosh.com/sent`. Cool. + +(No, I don't know what would happen if you created a label called `[Gmail]/All +Mail` in Gmail. If you try please let me know, but I take no responsibility if +it ends with all your email being deleted.) + +[maildir]: https://en.wikipedia.org/wiki/Maildir + + :::text + [Repository SteveLosh-Remote] + maxconnections = 1 + type = Gmail + remoteuser = steve@stevelosh.com + remotepasseval = get_keychain_pass(account="steve@stevelosh.com", server="imap.gmail.com") + realdelete = no + nametrans = lambda folder: {'[Gmail]/Drafts': 'drafts', + '[Gmail]/Sent Mail': 'sent', + '[Gmail]/Starred': 'flagged', + '[Gmail]/Trash': 'trash', + '[Gmail]/All Mail': 'archive', + }.get(folder, folder) + folderfilter = lambda folder: folder not in ['[Gmail]/Trash', + 'Nagios', + 'Django', + 'Flask', + '[Gmail]/Important', + '[Gmail]/Spam', + ] + +Finally, the home stretch. The last section described the folder on our local +hard drive, and this one describes our Gmail account. + +First, we tell offlineimap to only ever use a single connection at a time. You +can try increasing this number for better performance, but in my experience +Google is not afraid to enforce its rate limits and would cut me off fairly +often when I tried that. Just leave it at one if you want to be safe. + +Next is the type. Luckily offlineimap provides a `Gmail` type that handles +a lot of the craziness that is Gmail's IMAP setup. Nice. + +Then we have the username. Nothing special here, except that if you have +a non-apps account (i.e.: an actual vanilla Gmail account) you may or may not +need to include the `@gmail.com` in the username. I don't know. If one doesn't +work, just try the other. + +Next we have `remotepasseval`. This is a bit of Python code (drink!) that +should return the password for the account. + +What is this `get_keychain_pass` function? Well, remember when we saw the +`pythonfile` setting back in the general section? It's a function defined in +there. I'll talk about that in the next section, for now just accept that it +works. + +Next we set `realdelete` to no. If this is set to yes, then deleting an email +in your inbox would actually delete it entirely. When you set it to no, then +deleting an email from your inbox (or any label's folder) will leave it in +Gmail's All Mail. + +If you want to really delete an email, you'll need to delete it from All Mail +(which is named archive on our local filesystem, remember?). I feel like this +is a good compromise. I rarely care about actually deleting mail, given that +I have many unused gigabytes available on Gmail. + +Next we have another nametrans setting. This is a Python function (drink!) just +like the one for the local repository, except it goes in the other direction. +It takes the name of a local folder and returns the name of the folder on the +IMAP server. Knowing this, it should be easy to understand this setting. + +Finally, we have `folderfilter`. This is a Python function (drink!) that takes +a **remote** folder name and returns `True` if that folder should be synced, or +`False` if it should **not** be synced. I've chosen to skip syncing my Spam and +Trash folders, as well as a few mailing list labels I don't check all that +often. Customize this to your own taste. + +### Retrieving Passwords + +We're almost ready, but there's one more thing we need to do, and that's +implement a secure way for offlineimap to get access to our Gmail password. + +If you don't care too much about security, you *can* configure offlineimap with +a plaintext password right in the config file. But don't do that. It'll only +take a minute to do this securely. + +First, you need to add your Gmail password into your OS X keychain. Open the +Keychain Access app and press the `+` button: + +![Keychain 1](/media/images{{ parent_url }}/keychain-1.png) + +Then fill out the form. The "Keychain Item Name" should be +`http://imap.gmail.com`. The "Account Name" should be your email address. The +password should be your password: + +![Keychain 2](/media/images{{ parent_url }}/keychain-2.png) + +Press "Add". Now repeat the process for the SMTP server. The "Keychain Item +Name" should be `smtp://smtp.gmail.com`. The "Account Name" should be your +email address. The password should be your password: + +![Keychain 3](/media/images{{ parent_url }}/keychain-3.png) + +Now we need to create the `offlineimap.py` file we pointed offlineimap to +earlier. It needs to contain the `get_keychain_pass` function, which takes an +`account` and `server` and return the password. Here's the file I'm using: + + :::python + #!/usr/bin/python + import re, subprocess + def get_keychain_pass(account=None, server=None): + params = { + 'security': '/usr/bin/security', + 'command': 'find-internet-password', + 'account': account, + 'server': server, + 'keychain': '/Users/sjl/Library/Keychains/login.keychain', + } + command = "sudo -u sjl %(security)s -v %(command)s -g -a %(account)s -s %(server)s %(keychain)s" % params + output = subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT) + outtext = [l for l in output.splitlines() + if l.startswith('password: ')][0] + + return re.match(r'password: "(.*)"', outtext).group(1) + +In a nutshell, it uses `/usr/bin/security` to retrieve the password. Read +through the code if you're curious. + +This is not *completely* secure, but it's better than having your password in +a plaintext file in your home directory. + +Whew! Time to actually run this thing and pull down our email! + +### Running offlineimap + +Assuming everything is in place, open a terminal and run offlineimap: + + offlineimap + +Go read a book, because this is going to pull down all the email (with +attachments) in any folders you didn't exclude in the config file. + +**If there's an error, stop and figure out what went wrong**. Remember, +offlineimap is a *two-way* sync, so there's always the possibility it'll eat +your email if you seriously mess something up! I wish it had a +`--dont-touch-remote` option you could use as a safety net for the original +sync, but it doesn't, so be careful! + +In the future you can use `offlineimap -q` to run it in "quick mode". It'll +perform fewer checks but will generally be much faster. + +If you want to set up offlineimap to run every 5 minutes or so, you can use +`launchd`. `cron` does not work for some reason. I'm not entirely sure why. + +Personally I actually *like* having to press a key to fetch new mail. It's less +of a distraction than having new mail rolling in all the time. I can get new +email when I'm ready to actually look at it, rather than having it nagging me +all the time. + +The great part about offlineimap is that once you've got it configured and it's +successfully run once, it's pretty much rock solid from then on. You can run it +often, `Ctrl-c` it, put the laptop to sleep in the middle of a run, or `kill -9` +it, and it still won't lose emails. On the next sync it'll fix anything that's +missing. + +Mutt! +----- + +Now that you've got your email on your computer, it's finally time to start +using Mutt itself! + +### Installing + +Mutt can be installed in a bunch of different ways, but the easiest is through +Homebrew: + + :::text + brew install mutt --sidebar-patch + +The sidebar patch is a third-party patch that adds a sidebar to Mutt. I don't +know why it's not in core Mutt because it's insanely useful. Oh well, at least +Homebrew makes it simple to get. + +That's pretty much it for installation, but don't get too relaxed because you're +far from done. + +### Configuring + +Mutt is *very* configurable. This is great once you've become a power user and +want to mold it to your will, but terrible when you're just getting started. + +Mutt settings are kept in a `~/.muttrc` file. If this file doesn't exist Mutt +will look for `~/.mutt/muttrc` (note the lack of a dot in the filename), so you +can put it there if you prefer. + +Let's start by creating a basic `~/.muttrc` piece by piece (a lot of this was +taken from [this article][pris]). + +Once you've got a bit of Mutt under your belt you'll want to read [the +documentation][muttdoc] for these settings, but for now just use them to keep +things sane. + +[pris]: http://pbrisbin.com/posts/two_accounts_in_mutt +[muttdoc]: http://www.mutt.org/doc/manual/manual-6.html + + :::text + # Paths ---------------------------------------------- + set folder = ~/.mail # mailbox location + set alias_file = ~/.mutt/alias # where to store aliases + set header_cache = ~/.mutt/cache/headers # where to store headers + set message_cachedir = ~/.mutt/cache/bodies # where to store bodies + set certificate_file = ~/.mutt/certificates # where to store certs + set mailcap_path = ~/.mutt/mailcap # entries for filetypes + set tmpdir = ~/.mutt/temp # where to keep temp files + set signature = ~/.mutt/sig # my signature file + +Here we tell Mutt where to find the various folders it needs. + + :::text + # Basic Options -------------------------------------- + set wait_key = no # shut up, mutt + set mbox_type = Maildir # mailbox type + set timeout = 3 # idle time before scanning + set mail_check = 0 # minimum time between scans + unset move # gmail does that + set delete # don't ask, just do + unset confirmappend # don't ask, just do! + set quit # don't ask, just do!! + unset mark_old # read/new is good enough for me + set beep_new # bell on new mails + set pipe_decode # strip headers and eval mimes when piping + set thorough_search # strip headers and eval mimes before searching + +These are some basic options to make Mutt behave a bit more sanely. + + :::text + # Sidebar Patch -------------------------------------- + set sidebar_delim = ' │' + set sidebar_visible = yes + set sidebar_width = 24 + color sidebar_new color221 color233 + +These options are specific to the sidebar patch. + + :::text + # Status Bar ----------------------------------------- + set status_chars = " *%A" + set status_format = "───[ Folder: %f ]───[%r%m messages%?n? (%n new)?%?d? (%d to delete)?%?t? (%t tagged)? ]───%>─%?p?( %p postponed )?───" + +This gives us a pretty status bar with the information we care about (and none +of the stuff we don't). + + :::text + # Header Options ------------------------------------- + ignore * # ignore all headers + unignore from: to: cc: date: subject: # show only these + hdr_order from: to: cc: date: subject: # and in this order + +These options hide some of the extra email headers we don't care about when +viewing and composing email. + +Now it's time to fill on our account details: + + :::text + # Account Settings ----------------------------------- + + # Default inbox. + set spoolfile = "+steve-stevelosh.com/INBOX" + + # Alternate email addresses. + alternates sjl@pculture.org still\.?life@gmail.com steve@ladyluckblues.com steve@pculture.org + + # Mailboxes to show in the sidebar. + mailboxes +steve-stevelosh.com/INBOX \ + +steve-stevelosh.com/vim \ + +steve-stevelosh.com/clojure \ + +steve-stevelosh.com/python \ + +steve-stevelosh.com/mercurial \ + +steve-stevelosh.com/archive \ + +steve-stevelosh.com/sent \ + +steve-stevelosh.com/drafts \ + + # Other special folders. + set mbox = "+steve-stevelosh.com/archive" + set postponed = "+steve-stevelosh.com/drafts" + +Most of those should be self-explanatory. Fill in the appropriate values for +your mail folder(s). + +We'll add more as we go through the next few sections, but that's enough to get +us started. + +### Running + +Now that you've got Mutt configured you can run it: + + :::sh + mutt + +I like to always be in my `~/Desktop` folder when in Mutt, so that when I save +emails or attachments they go there by default. I have a little shell function +set up that `cd`s there for me before running Mutt: + + :::sh + alias mutt 'cd ~/Desktop && mutt' + +If you run the [new fish shell][fish], this is going to cause problems later +(long story, but it's related to the `read` builtin). Do yourself a favor and +head those confusing issues off at the pass with a fish function: + + :::text + function mutt + bash --login -c 'cd ~/Desktop; /usr/local/bin/mutt' $argv; + end + +Remember that if you use another shell like this you'll want to set up any +aliases and your `PATH` for that shell properly (probably identically to your +main shell). + +[fish]: http://ridiculousfish.com/shell/ + +Reading Email +------------- + +Once you start Mutt you should be looking at a list of the email in your inbox. +If so: congratulations! If not: stop and figure out what went wrong. + +### The Index + +When viewing a folder, Mutt presents you with a list of your email. This view +is called the "index": + +![Mutt's Index](/media/images{{ parent_url }}/mutt-index.png) + +This entry isn't meant be a guide to setting up Mutt on OS X. For a full guide +on how to *use* Mutt, you can Google around for some tutorials, or just learn as +you go with `?`. The `?` key will show you a list of all the keys you can use +wherever you currently are, and what they do. + +Let's add a few lines to our `~/.muttrc` to make the index view behave a bit +more nicely: + + :::text + # Index View Options --------------------------------- + set date_format = "%m/%d" + set index_format = "[%Z] %D %-20.20F %s" + set sort = threads # like gmail + set sort_aux = reverse-last-date-received # like gmail + set uncollapse_jump # don't collapse on an unread message + set sort_re # thread based on regex + set reply_regexp = "^(([Rr][Ee]?(\[[0-9]+\])?: *)?(\[[^]]+\] *)?)*" + +I won't go into what those do here. You can read the documentation if you're +curious. + +Quit and rerun Mutt to see your changes. Mutt is a very lightweight program so +this should be fast. + +Let's also add a few key bindings in the index to make it easier to use: + + :::text + # Index Key Bindings --------------------------------- + bind index gg first-entry + bind index G last-entry + + bind index R group-reply + bind index sync-mailbox + bind index collapse-thread + + # Ctrl-R to mark all as read + macro index \Cr "T~UN." "mark all messages as read" + + # Sync email + macro index O "offlineimap" "run offlineimap to sync all mail" + macro index o "offlineimap -qf INBOX" "run offlineimap to sync inbox" + + # Saner copy/move dialogs + macro index C "?" "copy a message to a mailbox" + macro index M "?" "move a message to a mailbox" + +Remember to quit and rerun Mutt for them to take effect. + +We're going to use `j` and `k` to move around, so we may as well support Vim +keys like `gg` and `G` too. We'll use `R` for reply all, since that comes in +handy fairly often. `Ctrl-R` will mark all messages in the current folder as +read. + +Don't worry if you don't understand how all these bindings and macros work right +now. You can read the documentation later. + +The `tab` key is going to "commit" changes we've made in Mutt (like deleting an +email) to our local Maildir folder. Once those changes are in the Maildir +folder offlineimap will sync them to the server the next time it runs. This is +nice because it lets us recover if we accidentally do something stupid like +deleting the wrong email. + +**Note:** Mutt will also sync changes for a folder when you switch to +a different folder, and when you quit Mutt, so be aware of those. + +The `space` key will toggle collapsing of threads, which can be convenient when +viewing mailing lists (or any conversations with many messages). + +The `o` and `O` keys will run offlineimap to sync mail. Like I said before, +I prefer having to press a button to grab mail instead of it constantly grabbing +and nagging me. `o` will sync only the inbox (fast), and `O` will sync +everything (much slower). + +Finally we rebind `C` and `M` to perform the same operations they usually do, +but in a more user-friendly manner. + +While we're at it, let's add a way to navigate around the sidebar so we can +switch folders: + + :::text + # Sidebar Navigation --------------------------------- + bind index,pager sidebar-next + bind index,pager sidebar-prev + bind index,pager sidebar-open + +We're binding the `up` and `down` arrow keys to switch between folders, and +`right` to "enter" a folder. Give it a try. + +We don't need the arrows because we can navigate with `j` and `k`, but if you +prefer to rebind them to something else feel free. + +Practice moving between folders and around in the list, then we'll move on to +actually reading emails. + +### The Pager + +Press `return` in the index to open the selected email. This view is called the +pager: + +![Mutt's Pager](/media/images{{ parent_url }}/mutt-pager.png) + +Like before, let's add a few settings: + + :::text + # Pager View Options --------------------------------- + set pager_index_lines = 10 # number of index lines to show + set pager_context = 3 # number of context lines to show + set pager_stop # don't go to next message automatically + set menu_scroll # scroll in menus + set tilde # show tildes like in vim + unset markers # no ugly plus signs + + set quote_regexp = "^( {0,4}[>|:#%]| {0,4}[a-z0-9]+[>|]+)+" + alternative_order text/plain text/enriched text/html + +This is a good, sane starting point. And now for a few extra key bindings: + + :::text + # Pager Key Bindings --------------------------------- + bind pager k previous-line + bind pager j next-line + bind pager gg top + bind pager G bottom + + bind pager R group-reply + + # View attachments properly. + bind attach view-mailcap + +The first few make scrolling behave like it does in the index. We're also going +to use the same key for reply all here. Consistency will make it easier to get +Mutt into your fingers. + +Don't worry about the last one -- that's to make sure Mutt treats attachments +properly. + +Go ahead and try reading some emails. Remember that `?` will always give you +a list of keys and their functions. + +### Attachments + +Now that we're all set for reading plain text email, it's time to deal with +attachments. + +When you're in the pager view reading an email with attachments, you can press +`v` to view a list of them: + +![Attachment List](/media/images{{ parent_url }}/mutt-attachments.png) + +Scroll through the list with `j` and `k` and press `return` to view one. But +first we need to tell Mutt how to view things that aren't text! + +For that we need to create a `~/.mutt/mailcap` file. Here's a sample to get you +started: + + :::text + # MS Word documents + application/msword; ~/.mutt/view_attachment.sh %s "-" '/Applications/TextEdit.app' + + # Images + image/jpg; ~/.mutt/view_attachment.sh %s jpg + image/jpeg; ~/.mutt/view_attachment.sh %s jpg + image/pjpeg; ~/.mutt/view_attachment.sh %s jpg + image/png; ~/.mutt/view_attachment.sh %s png + image/gif; ~/.mutt/view_attachment.sh %s gif + + # PDFs + application/pdf; ~/.mutt/view_attachment.sh %s pdf + + # HTML + text/html; ~/.mutt/view_attachment.sh %s html + + # Unidentified files + application/octet-stream; ~/.mutt/view_attachment.sh %s "-" + +The `view_attachment.sh` script is from [here][attachment]. Here's a link to +[my copy][attachment-mirror] in case that site ever goes down. Grab the script, +chmod it to executable, and stick it in `~/.mutt`. + +You can poke around and figure out how it works, or you can just not worry about +it and get on with life. I recommend the latter (at least for now). + +Now you can press `return` to open an attachment in the proper program. + +[attachment]: http://linsec.ca/Using_mutt_on_OS_X#mailcap +[attachment-mirror]: https://bitbucket.org/sjl/dotfiles/src/tip/mutt/view_attachment.sh + +### URLs + +One thing you'll probably want to do while reading email is open links. Many +terminal programs like iTerm2 let you command-click on a link to open it, but +this is Mutt! We shouldn't have to use the mouse! + +We're going to use a small helper program called urlview to make it easy to open +URLs in email. First, install it with `brew install urlview`. Then make +a `~/.urlview` file with the following contents: + + :::text + COMMAND open %s + +This tells urlview what command to use to open a URL. We're just going to use +the OS X `open` command to do the right thing. + +Next, add the following line to your `~/.muttrc`: + + :::text + macro pager \Cu "|urlview" "call urlview to open links" + +Now when you're reading an email with links in it you can press `Ctrl-u` to open +urlview. You'll see a screen like this: + +![urlview screen](/media/images{{ parent_url }}/mutt-urls.png) + +Navigate with `j`, `k`, `gg`, `G`, or `/` and press `return` when the desired +link is selected. That link will be filled in at the bottom of the screen in +case you want to edit it, and you can press `return` one more time to actually +open it in your default browser. + +That about wraps it up for reading email. Now it's time to write some! + +Writing Email +------------- + +Writing email is one of the best parts of Mutt. First let's add a few settings +to get things nice and sane: + + :::text + # Compose View Options ------------------------------- + set realname = "Steve Losh" # who am i? + set envelope_from # which from? + set sig_dashes # dashes before sig + set edit_headers # show headers when composing + set fast_reply # skip to compose when replying + set askcc # ask for CC: + set fcc_attach # save attachments with the body + unset mime_forward # forward attachments as part of body + set forward_format = "Fwd: %s" # format of subject when forwarding + set forward_decode # decode when forwarding + set attribution = "On %d, %n wrote:" # format of quoting header + set reply_to # reply to Reply to: field + set reverse_name # reply as whomever it was to + set include # include message in replies + set forward_quote # include message in forwards + +You can reply to an email with `r` in the index or pager, or start a fresh one +with `m`. + +There's actually not a lot to say about writing mail, because Mutt itself +doesn't handle it! Mutt passes control off to the text editor of your choice. +Just specify your editor in your `~/.muttrc`: + + :::text + set editor = "vim" # Use terminal Vim to compose email. + set editor = "mvim -f" # Use MacVim to compose email. + set editor = "subl -w" # Use Sublime Text 2 to compose email. + +Any command that takes a filename and doesn't return until you're done can be +used here. + +This is fantastic because it means you can use an editor you're already +comfortable and fast in to write email instead of learning yet another set of +shortcuts. + +Once you save the email in your editor and close it, Mutt will present you with +a menu that looks like this: + +![Sending Screen](/media/images{{ parent_url }}/mutt-send-1.png) + +You can press `e` to go back and edit the mail, `a` to add attachments, and so +on (the options are listed at the top of the screen). + +Before we can continue we need to tell Mutt how to send email. Press `q` to +discard the email for now. + +Sending Email +------------- + +Mutt does have (some) built-in SMTP support, but we're going to use a separate +program to do our sending for a few reasons. + +First, Mutt's SMTP support was considered "experimental" the last time +I checked. Sending email is kind of important, so we'll stick with something +tried and true. + +Second, we want a method that won't require our password in a plaintext config +file. + +Go ahead and install the `msmtp` program through Homebrew with `brew install +msmtp`. + +Next we're going to need to create a `~/.msmtprc` file with the following +contents: + + :::text + account stevelosh + host smtp.gmail.com + port 587 + protocol smtp + auth on + from steve@stevelosh.com + user steve@stevelosh.com + tls on + tls_trust_file ~/.mutt/Equifax_Secure_CA.cert + + account default : stevelosh + +`msmtp` will look in your keychain for your SMTP password, which we added +earlier. No plaintext passwords! + +The other "interesting" bit here is the `tls_trust_file`. We're going to be +connecting to Gmail's SMTP server over SSL, and `msmtp` needs to know if it can +trust the certificate that the server on the other end is sending back. + +Copy the following and paste it into the path `tls_trust_file` is set to: + + :::text + -----BEGIN CERTIFICATE----- + MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJVUzEQMA4GA1UE + ChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 + MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoT + B0VxdWlmYXgxLTArBgNVBAsTJEVxdWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCB + nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPR + fM6fBeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+AcJkVV5MW + 8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kCAwEAAaOCAQkwggEFMHAG + A1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UE + CxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoG + A1UdEAQTMBGBDzIwMTgwODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvS + spXXR9gjIBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQFMAMB + Af8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUAA4GBAFjOKer89961 + zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y7qj/WsjTVbJmcVfewCHrPSqnI0kB + BIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee95 + 70+sB3c4 + -----END CERTIFICATE----- + +If you're paranoid and don't trust that I'm giving you the right cert (or that +someone has hacked my site and changed it), you can generate it yourself. I'll +leave that to you -- if you care enough about it you'll figure it out. + +Now we need to tell Mutt to use msmtp. Add the following to your `~/.muttrc` +file: + + :::text + set from = "steve@stevelosh.com" + set sendmail = "/usr/local/bin/msmtp -a stevelosh" + set sendmail_wait = 0 + unset record + +The `-a stevelosh` will need to change to whatever you named your account in the +msmtp config. + +The `unset record` line tells Mutt to not append a copy of every email you send +to a file on your hard drive. Gmail will save the emails you send in the sent +folder, so you'll get the the next time you sync with offlineimap anyway. + +The `sendmail_wait` line tells Mutt to wait for the msmtp program to finish +sending the mail before returning control, instead of running it in the +background. This makes it obvious if there's a problem sending a message, which +I prefer to silent, backgrounded failures. + +Now you can send email! Awesome! + +Once you've composed a test email and saved it you'll be presented with a screen +like this (which we saw in the previous section): + +![Sending Screen](/media/images{{ parent_url }}/mutt-send-1.png) + +The keys you need are listed along the top. Pressing `y` now will invoke msmtp +and send your email! + +You'll see "Sending message..." at the bottom of the screen while msmtp is +working. If there's a problem, Mutt will tell you the error. Figure it out +before moving on. + +Postponing Drafts +----------------- + +Sometimes I like to read and respond to email without an internet connection, +then actually send the replies when I get back to civilization. + +Since all of my email is stored locally, reading it offline is trivial. + +To reply or compose offline, I use Mutt's "postpone" feature. First I've added +the following line to my `~/.muttrc`: + + :::text + bind compose p postpone-message + +Now when I'm at the sending screen instead of pressing `y` to send I can press +`p` to postpone the message. This places the message in the drafts folder. + +The drafts folder in Mutt is just a normal folder of emails like any other. +When you sync with offlineimap your postponed email will get pushed up to Gmail +as a draft. You can edit it in the Gmail web interface if you like, and those +edits will sync back down too. + +If you want to edit your drafts (postponed messages) locally, you need to +"recall" them. I have the following line in my `~/.muttrc` to set the key to +the same one as I used to postpone it in the first place: + + :::text + bind index p recall-message + +You can also use `m` to start writing a new email, and mutt will prompt you if +there are existing postponed messages. + +Once you hit `p` (or `m` and then select "yes") Mutt will show you a list of the +postponed messages. Select one and press `return` to start editing it again. +If there's only one it will skip this step and simply open it for you +immediately. + +Once you're done editing you can either postpone it again or send it as usual. + +Unfortunately you have to go through the "recall → edit → send" process each +time. As far as I know there's no way to simply run down a list of postponed +emails sending each one with a single keystroke. But that's not *too* bad, and +it's great to be able to work offline like this. + +Once you send the postponed email (through Mutt or the Gmail web interface) it +disappears from the drafts folder and the postponed list as you would expect. + +Contacts +-------- + +Next we'll want to get Mutt to autocomplete our contacts from the OS X address +book. I like using the OS X address book because it automatically syncs between +my laptops and phone, so I only need to maintain one address list. + +### Autocompleting + +Unfortunately I've got some bad news for you: you're going to need to install +XCode. + +No, not the command-line developer tools. The full XCode. I'm sorry, but trust +me when I say it's going to save you a lot of pain, so just grumble to yourself +a bit and do it. + +Okay, now that you've got XCode you can install the `contacts` program through +Homebrew with `brew install contacts`. + +`contacts` is a command-line program that you can use to query your address +book. To tell Mutt how to use it add the following lines to your +`~/.mutt/muttrc`: + + :::text + set query_command = "contacts -Sf '%eTOKEN%n' '%s' | sed -e 's/TOKEN/\t/g'" + bind editor complete-query + bind editor ^T complete + +Now when you're filling out an email address field you can type a few characters +and hit Tab to get a screen like this: + +![Contacts](/media/images{{ parent_url }}/mutt-contacts-1.png) + +You can use `j` and `k` to select an item, press return to complete it. Press +`q` if you've changed your mind and want to cancel the completion. Look at the +top of the screen for more handy little keys you can use here. + +If there's only one item in the list Mutt won't bother showing you this screen +and will just complete it right away. + +This completion searches more than just the email address. It'll also search +the names and possibly other fields from the address book entries as well. + +### Adding Contacts + +What about adding contacts to your address book? Any contacts you add on your +phone will automatically be synced, but what if you're reading your mail in Mutt +and just want to add the sender as a contact without leaving your command line? + +For this I use a little script that [Simone Manganelli][simx] wrote for me +called `addcontact`. You can [get it here][addcontact] and stick it in your +`$PATH` somewhere. It's just a command-line utility that you can use like this: + + :::text + $ addcontact Steve Losh steve@stevelosh.com + $ addcontact "Steve Losh" steve@stevelosh.com + $ addcontact Steve Losh work steve@pculture.org + +As you can see, it's pretty flexible. + +**Note:** This utility always adds a new contact record, so if you add someone +that's already in there you're going to get a duplicate entry. If that happens +you can search for the entries in Contacts.app, select the duplicates, and use +"Card → Merge Selected Cards" to combine them. + +Okay, so we can now add contacts from the command line. You could set up a Mutt +macro to automatically add senders without too much trouble, but I don't do +that. If I want to add a contact I just hit `!` and type out the shell command. +It's not that much work, and sometimes the name in the "From:" field is in +a weird format like "Last, First Middle" instead of "First Last", so this gives +me a chance to clean it up before I add it. + +[simx]: http://simx.me/ +[addcontact]: https://bitbucket.org/sjl/dotfiles/src/tip/bin/addcontact + +Searching Email +--------------- + +If you have more than a screen full of email in any given folder, you're going +to want a way to search through it. Mutt's built-in searching is a good start, +but I also use another program to get a bit more power. + +### Vanilla Searching + +There are two main ways to search your email in Mutt: plain searching and +"limiting". + +Plain searching is done with the `/` key. It works similarly to Vim or less: +you press `/`, type your query, and press enter to perform the search. You can +use `n` to move to the next match, and the search will loop around to the top if +it hits the bottom. + +I have the following lines in my `~/.muttrc` to bind `N` to go to the *previous* +match: + + :::text + bind index N search-opposite + bind pager N search-opposite + +Normally the `N` key marks a message as unread (or "new"). I personally never +want to do that. Unread mail should be "mail that has not been read". If you +use that feature you'll want to rebind it to something else. + +Two things about queries: + +1. They are regular expressions (actually it's more powerful than than, see [the + documentation][patterns] for more information). +2. They only search the To and Subject fields (*not* the message bodies!). + +I generally use this kind of searching when I see the email I want to open on +the screen but don't feel like pressing `j` or `k` forty times to move through +the list. When I'm actually trying to find a message I can't already see on the +screen I use limiting. + +[patterns]: http://www.mutt.org/doc/manual/manual-4.html#ss4.2 + +### Vanilla Limiting + +Limiting is the other way Mutt provides for searching mail. It's done with the +`l` key by default. + +Like `/`, `l` will ask you for a pattern. But instead of simply moving you to +the next message that matches the pattern, Mutt will *hide* messages that don't +match. + +This lets you see all the ones that match in a single list. This list works +just like a normal list. You can search it with `/`, tag things, and so on as +you normally would. It's really quite nice once you get used to it. + +To remove the limiting once you're done, you can limit to the special value +`all`. I've added a line to my `~/.muttrc` so I can do that with a single key: + + :::text + macro index a "all\n" "show all messages (undo limit)" + +**Note:** This shadows the `create-alias` function which I never use. + +Limit queries work exactly like search queries, so you can do powerful stuff +like `~f arthur ~C honza ~s api` ("limit to messages from 'arthur', to or cced +to 'honza', containing 'api' in the subject"). + +### Full-Text Searching + +By now you're probably wondering how to search the full text of messages. There +are two ways: one simple and slow, the other complicated and fast. + +First is the simple, slow way. You can use `~B` in your searches and limits to +search inside the entire message. If your folder only has a hundred messages +this works great. But once you have a few more (my archive has about 30,000 at +the moment, and I prune it fairly often) it quickly becomes too slow to be +usable. + +I use a program called [notmuch][] to index and search my email. It's blazingly +fast and works pretty well. + +First, install it with `brew install notmuch`. Now you need +a `~/.notmuch-config` file. Run `notmuch setup` to generate one. It's pretty +straightforward. When it asks you for the path to your archive, that's the path +to the folder containing all your individual IMAP folders +(`~/.mail/steve-stevelosh.com` in my setup). + +Now run `notmuch new` to perform the initial index. It might take a while if +you have a lot of folders. Subsequent indexes will be much faster since they'll +only reindex whatever changed since the last time. + +You'll want to reindex when your email changes, so add a hook in your +`~/.offlineimaprc` to run `notmuch new` whenever offlineimap syncs your email: + + :::text + [Account SteveLosh] + localrepository = SteveLosh-Local + remoterepository = SteveLosh-Remote + status_backend = sqlite + postsynchook = notmuch new + +Now you can use `notmuch search foo` to search your mail for "foo" from the +command line. Try a couple of queries and make sure it works. + +Now we need to hook notmuch into Mutt. There are a number of different ways to +do this, all of them hacky and ugly. I'll describe how I do it. + +A quick overview of how this is going to work: + +1. You'll press a key in Mutt to activate searching. +2. You'll type your query and press return. +3. Your mail will be searched with `notmuch`. +4. Τhe resulting messages will be symlinked into a temporary maildir folder. +5. That temporary folder will be opened in Mutt. + +First get [mutt-notmuch-py][]. The original `mutt-notmuch` is a Perl script +with many external requirements that are a pain to install, and it doesn't work +on OS X. `mutt-notmuch-py` is a Python script with zero external requirements. + +`mutt-notmuch-py` is going to handle steps 2 through 4 in the list. Get the +script into your `$PATH` somehow and then run it: + + :::text + $ mutt-notmuch-py -G ~/.mail/temporary/search + Query: foo + + $ ls ~/.mail/temporary/search + cur new + +The `-G` tells it to not perform certain Gmail-specific stuff that we don't +need. The path is where it will create the temporary maildir folder with the +results. Each time you run it this folder will be wiped clean before the new +results are linked into it. + +Now we need to handle the first and last points in the list. I have the +following mapping in my `~/.muttrc`: + + :::text + macro index S "unset wait_keymutt-notmuch-py ~/.mail/temporary/search+temporary/search" "search mail (using notmuch)" + +That's a lot to take in, so let's see how it works piece by piece: + + :::text + macro index S + +We're going to use the `S` key to perform a full search of all of our mail. + + :::text + unset wait_key + +Unset the `wait_key` Mutt option to prevent Mutt from asking us to press a key +after the search has finished. + + :::text + mutt-notmuch-py -G ~/.mail/temporary/search + +Run `mutt-notmuch-py`. Control will pass over to that script and it will ask +you for your query, run the search, set up the results folder, and then hand +control back to Mutt. + + :::text + +temporary/search + +Now we change over to the temporary folder in Mutt, and we're now looking at +a list of search results! Since this is a real Maildir folder like any other +one we can use the full range of tools to interact with it (searching, limiting, +etc). + +Finally, let's get this search results folder in our sidebar so we can see where +we are visually at all times: + + :::text + mailboxes +steve-stevelosh.com/INBOX \ + +steve-stevelosh.com/vim \ + +steve-stevelosh.com/clojure \ + ... + +temporary/search \ + +Now the search results folder can be navigated like any other one. That's it +for email searching! Now you should have a setup that you can use in real life +to manage your email. + +[notmuch]: http://notmuchmail.org/ +[mutt-notmuch-py]: https://github.com/honza/mutt-notmuch-py + +Conclusion +---------- + +Mutt is definitely a beast. It's old, crufty, and ugly, but if you spend the +time to set it up and learn to use it you'll be rewarded with a fast, powerful, +customizable environment for working with your email. + +A wonderful trend these days is that more and more sites are including the +ability to respond to comments and such by simply replying to their notification +emails. This means that often you can reply to Facebook emails, comment on +GitHub pull requests, and respond to Bitbucket issues all without leaving the +comfort of your finely-tuned email client. + +Mutt's not for everyone, but if you deal with a lot of email and have some time +to spend you should give it a try. You just might learn to love the old dog. + +{% endblock article %} diff -r fe3eb7796d61 -r bd15116835e2 media/images/blog/2012/07/keychain-1.png Binary file media/images/blog/2012/07/keychain-1.png has changed diff -r fe3eb7796d61 -r bd15116835e2 media/images/blog/2012/07/keychain-2.png Binary file media/images/blog/2012/07/keychain-2.png has changed diff -r fe3eb7796d61 -r bd15116835e2 media/images/blog/2012/07/keychain-3.png Binary file media/images/blog/2012/07/keychain-3.png has changed diff -r fe3eb7796d61 -r bd15116835e2 media/images/blog/2012/07/mutt-contacts-1.png Binary file media/images/blog/2012/07/mutt-contacts-1.png has changed diff -r fe3eb7796d61 -r bd15116835e2 media/images/blog/2012/07/mutt-quotes-1.png Binary file media/images/blog/2012/07/mutt-quotes-1.png has changed diff -r fe3eb7796d61 -r bd15116835e2 media/images/blog/2012/07/mutt-send-1.png Binary file media/images/blog/2012/07/mutt-send-1.png has changed diff -r fe3eb7796d61 -r bd15116835e2 media/images/blog/2012/07/what-the-mutt.png Binary file media/images/blog/2012/07/what-the-mutt.png has changed diff -r fe3eb7796d61 -r bd15116835e2 media/images/blog/2012/10/keychain-1.png Binary file media/images/blog/2012/10/keychain-1.png has changed diff -r fe3eb7796d61 -r bd15116835e2 media/images/blog/2012/10/keychain-2.png Binary file media/images/blog/2012/10/keychain-2.png has changed diff -r fe3eb7796d61 -r bd15116835e2 media/images/blog/2012/10/keychain-3.png Binary file media/images/blog/2012/10/keychain-3.png has changed diff -r fe3eb7796d61 -r bd15116835e2 media/images/blog/2012/10/mutt-attachments.png Binary file media/images/blog/2012/10/mutt-attachments.png has changed diff -r fe3eb7796d61 -r bd15116835e2 media/images/blog/2012/10/mutt-contacts-1.png Binary file media/images/blog/2012/10/mutt-contacts-1.png has changed diff -r fe3eb7796d61 -r bd15116835e2 media/images/blog/2012/10/mutt-index.png Binary file media/images/blog/2012/10/mutt-index.png has changed diff -r fe3eb7796d61 -r bd15116835e2 media/images/blog/2012/10/mutt-pager.png Binary file media/images/blog/2012/10/mutt-pager.png has changed diff -r fe3eb7796d61 -r bd15116835e2 media/images/blog/2012/10/mutt-ready-to-send.png Binary file media/images/blog/2012/10/mutt-ready-to-send.png has changed diff -r fe3eb7796d61 -r bd15116835e2 media/images/blog/2012/10/mutt-send-1.png Binary file media/images/blog/2012/10/mutt-send-1.png has changed diff -r fe3eb7796d61 -r bd15116835e2 media/images/blog/2012/10/mutt-urls.png Binary file media/images/blog/2012/10/mutt-urls.png has changed diff -r fe3eb7796d61 -r bd15116835e2 media/images/blog/2012/10/what-the-mutt.png Binary file media/images/blog/2012/10/what-the-mutt.png has changed