blog post on integrating Mutt and Org-mode
authorStefano Zacchiroli <zack@upsilon.cc>
Mon, 8 Feb 2010 14:09:16 +0000 (15:09 +0100)
committerStefano Zacchiroli <zack@upsilon.cc>
Mon, 8 Feb 2010 14:09:16 +0000 (15:09 +0100)
blog/archives/2010/02.mdwn [new file with mode: 0644]
blog/posts/2010/02.mdwn [new file with mode: 0644]
blog/posts/2010/02/integrating_Mutt_with_Org-mode.mdwn [new file with mode: 0644]
blog/posts/2010/02/integrating_Mutt_with_Org-mode/emacs-conf [new file with mode: 0644]
blog/posts/2010/02/integrating_Mutt_with_Org-mode/mutt-conf [new file with mode: 0644]
blog/posts/2010/02/integrating_Mutt_with_Org-mode/mutt-open [new file with mode: 0644]
blog/posts/2010/02/integrating_Mutt_with_Org-mode/remember-mail [new file with mode: 0644]

diff --git a/blog/archives/2010/02.mdwn b/blog/archives/2010/02.mdwn
new file mode 100644 (file)
index 0000000..4598418
--- /dev/null
@@ -0,0 +1 @@
+[[!template id=archive_month year="2010" month="02"]]
diff --git a/blog/posts/2010/02.mdwn b/blog/posts/2010/02.mdwn
new file mode 100644 (file)
index 0000000..91aac93
--- /dev/null
@@ -0,0 +1 @@
+[[!meta redir=archives/2010/02]]
diff --git a/blog/posts/2010/02/integrating_Mutt_with_Org-mode.mdwn b/blog/posts/2010/02/integrating_Mutt_with_Org-mode.mdwn
new file mode 100644 (file)
index 0000000..5b81183
--- /dev/null
@@ -0,0 +1,133 @@
+# remember Mutt's mail in Org-mode and jump back to them
+
+As [[already anticipated|2009/10/mail_indexing_for_mutt]], I've been
+implementing my own [[!wikipedia Getting_Things_Done]] work flow. This post
+documents one of its main bricks: the **integration between
+[Mutt](http://www.mutt.org/) and [Org-mode](http://orgmode.org)**.
+
+As a geek, my main incoming stream of TODO items and information in general is
+email. As emails hit my INBOX, I either deal with them immediately (reply,
+archive, delete) or I need to store them elsewhere, possibly adding extra
+information such as a deadline, a personal note, the associated next action,
+the context in which it is actionable, etc. This need of adding extra
+information is what defeats the usage of my mail client (Mutt) as a list
+manager (in the GTD sense), and that's where I plugged Org-mode in. My main
+goals are:
+
+1. **create Org-mode notes from Mutt**, referencing the current email, and
+   possibly inlining some of its metadata (e.g. subject, sender)
+
+2. quickly **retrieve referenced emails from Org-mode notes**; ideally that
+   should happen in my usual email environment (i.e. Mutt), so that I can
+   process the retrieved mail as usual (e.g. to inform the sender that I did
+   something about it)
+
+<small> In fact, both would be straightforward to achieve if I were using some
+Emacs-based mail client such as [Gnus](http://www.gnus.org/), but
+[[I resist the *Emacs operating system syndrome*|2008/11/from_Vim_to_Emacs_-_part_2]],
+and therefore I insist in using my beloved Mutt. </small>
+
+Let's see how the two parts of the interaction between Mutt and Org-mode work.
+
+## <small>(1)</small> Mutt &rarr; Org-mode <small><em>(there ...)</em></small>
+
+The interaction from Mutt to Org-mode happens via
+[org-protocol](http://orgmode.org/worg/org-contrib/org-protocol.php). Using it
+external applications can feed content to Org-mode note templates, which are
+then interactively edited (via
+[emacsclient](http://www.emacswiki.org/emacs/EmacsClient)), and finally filed
+away.
+
+The Mutt glue macro from my `~/.muttrc` is as follow:
+
+       macro index \eR "|~/bin/remember-mail\n"
+
+The [[remember-mail]] script is trivial: it parses the fed mail from STDIN
+(using a couple of legacy Perl modules) and then invokes org-protocol.
+
+The relevant configuration from my `~/.emacs` is reported below; the comments
+explain the various parts:
+
+       ;; standard org <-> remember stuff, RTFM
+       (org-remember-insinuate)
+       (setq org-default-notes-file "~/org/gtd.org")
+       (setq org-remember-templates  ;; mail-specific note template, identified by "m"
+             '(("Mail" ?m "* %?\n\n  Source: %u, %c\n  %i" nil)))
+
+       ;; ensure that emacsclient will show just the note to be edited when invoked
+       ;; from Mutt, and that it will shut down emacsclient once finished;
+       ;; fallback to legacy behavior when not invoked via org-protocol.
+       (add-hook 'org-remember-mode-hook 'delete-other-windows)
+       (setq my-org-protocol-flag nil)
+       (defadvice org-remember-finalize (after delete-frame-at-end activate)
+         "Delete frame at remember finalization"
+         (progn (if my-org-protocol-flag (delete-frame))
+                (setq my-org-protocol-flag nil)))
+       (defadvice org-remember-kill (after delete-frame-at-end activate)
+         "Delete frame at remember abort"
+         (progn (if my-org-protocol-flag (delete-frame))
+                (setq my-org-protocol-flag nil)))
+       (defadvice org-protocol-remember (before set-org-protocol-flag activate)
+         (setq my-org-protocol-flag t))
+
+The result is that when you hit `ESC-R` in Mutt, emacsclient will be fired up
+in place presenting a note template that already contains relevant mail
+information (date, subject, from) and lets you add extra information before
+going away. Additionally, the email message-id will be hidden in the note as a
+`mutt:` hyperlink with anchor text "mail".
+
+## <small>(2)</small> Org-mode &rarr; Mutt <small><em>(... and back again)</em></small>
+
+Going back means that clicking on a "mail" hyperlink within an Org-mode note
+should bring up a Mutt instance showing the original message, in its context
+(e.g. its own mailbox). Achieving that consists of 2 separate steps:
+
+1. looking up a specific message by `Message-ID`
+2. firing up Mutt on the looked up message
+
+For the first part I use [[!debpkgsid maildir-utils]] (AKA
+[mu](http://code.google.com/p/mu0/)): a Xapian-based mail indexing tool, which
+nicely integrates with Mutt; check out
+[[my previous blog post on the subject|2009/10/mail_indexing_for_mutt/]] for a
+sample setup. Using mu, `Message-ID` lookups are as simple as:
+
+       zack@usha:~$ mu find -f p m:E1NbJad-0007x9-B7@ries.debian.org
+       /home/zack/Maildir/Debian.project/cur/1264883664_0.9472.usha,U=6320,FMD5=2284e927bb93d8a2ec434f5614dc04ba:2,S
+
+<small> Note: I'm relying upon *maildir-utils version 0.6* or greater, for all
+presented scripts. </small>
+
+For the second part I use the [[mutt-open]] script which fires upon a Mutt
+instance on the maildir containing a specific message, and then "hits" the
+appropriate keys to open the message and shutdown
+[the sidebar](http://packages.debian.org/lenny/mutt-patched) (if desired). It
+is a nicely reusable script, which I've being using elsewhere too.
+
+The needed glue on the emacs side is just a function to invoke `mutt-open` in a
+brand new terminal, and its declaration as the handler for `mutt:` URLs.
+
+       (defun open-mail-in-mutt (message)
+         "Open a mail message in Mutt, using an external terminal.
+
+       Message can be specified either by a path pointing inside a
+       Maildir, or by Message-ID."
+         (interactive "MPath or Message-ID: ")
+         (shell-command
+          (format "gnome-terminal -e \"%s %s\""
+                  (substitute-in-file-name "$HOME/bin/mutt-open") message)))
+
+       ;; add support for "mutt:ID" links
+       (org-add-link-type "mutt" 'open-mail-in-mutt)
+
+Voilà!
+
+## Download
+
+Summary of scripts and configuration snippets discussed above:
+
+* [[remember-mail]] - store in Org-mode a note back-referencing an email
+* [[mutt-open]] - open Mutt on an email having a specific `Message-ID`
+* [[Emacs configuration|emacs-conf]]
+* [[Mutt configuration|mutt-conf]]
+
+[[!tag lang/english planet-debian orgmode mutt gtd hack]]
diff --git a/blog/posts/2010/02/integrating_Mutt_with_Org-mode/emacs-conf b/blog/posts/2010/02/integrating_Mutt_with_Org-mode/emacs-conf
new file mode 100644 (file)
index 0000000..f1415dd
--- /dev/null
@@ -0,0 +1,34 @@
+;; standard org <-> remember stuff, RTFM
+(org-remember-insinuate)
+(setq org-default-notes-file "~/org/gtd.org")
+(setq org-remember-templates  ;; mail-specific note template, identified by "m"
+      '(("Mail" ?m "* %?\n\n  Source: %u, %c\n %i" nil)))
+
+;; ensure that emacsclient will show just the note to be edited when invoked
+;; from Mutt, and that it will shut down emacsclient once finished;
+;; fallback to legacy behavior when not invoked via org-protocol.
+(add-hook 'org-remember-mode-hook 'delete-other-windows)
+(setq my-org-protocol-flag nil)
+(defadvice org-remember-finalize (after delete-frame-at-end activate)
+  "Delete frame at remember finalization"
+  (progn (if my-org-protocol-flag (delete-frame))
+        (setq my-org-protocol-flag nil)))
+(defadvice org-remember-kill (after delete-frame-at-end activate)
+  "Delete frame at remember abort"
+  (progn (if my-org-protocol-flag (delete-frame))
+        (setq my-org-protocol-flag nil)))
+(defadvice org-protocol-remember (before set-org-protocol-flag activate)
+  (setq my-org-protocol-flag t))
+
+(defun open-mail-in-mutt (message)
+  "Open a mail message in Mutt, using an external terminal.
+
+Message can be specified either by a path pointing inside a
+Maildir, or by Message-ID."
+  (interactive "MPath or Message-ID: ")
+  (shell-command
+   (format "gnome-terminal -e \"%s %s\""
+          (substitute-in-file-name "$HOME/bin/mutt-open") message)))
+
+;; add support for "mutt:ID" links
+(org-add-link-type "mutt" 'open-mail-in-mutt)
diff --git a/blog/posts/2010/02/integrating_Mutt_with_Org-mode/mutt-conf b/blog/posts/2010/02/integrating_Mutt_with_Org-mode/mutt-conf
new file mode 100644 (file)
index 0000000..4cd5b6f
--- /dev/null
@@ -0,0 +1 @@
+macro index \eR "|~/bin/remember-mail\n"
diff --git a/blog/posts/2010/02/integrating_Mutt_with_Org-mode/mutt-open b/blog/posts/2010/02/integrating_Mutt_with_Org-mode/mutt-open
new file mode 100644 (file)
index 0000000..9bf9659
--- /dev/null
@@ -0,0 +1,64 @@
+#!/bin/bash
+#
+# Fire up mutt on a given mail, located in some Maildir
+# Mail can be specified either by path or by Messsage-ID; in the latter case
+# file lookup is performed using some mail indexing tool.
+#
+# Copyright: © 2009-2010 Stefano Zacchiroli <zack@upsilon.cc> 
+# License: GNU General Public License (GPL), version 3 or above
+
+# requires maildir-utils >= 0.6
+
+MUTT=mutt
+MUTT_FLAGS="-R"
+HIDE_SIDEBAR_CMD="B"   # set to empty string if sidebar is not used
+
+# Sample output of 'mu find -f p m:MESSAGE-ID', which gets passed to mutt-open
+#  /home/zack/Maildir/INBOX/cur/1256673179_0.8700.usha,U=37420,FMD5=7e33429f656f1e6e9d79b29c3f82c57e:2,S
+
+die_usage () {
+    echo "Usage: mutt-open FILE" 1>&2
+    echo "       mutt-open MESSAGE-ID" 1>&2
+    echo 'E.g.:  mutt-open `mu find -f p m:MESSAGE-ID`' 1>&2
+    echo '       mutt-open 20091030112543.GA4230@usha.takhisis.invalid' 1>&2
+    exit 3
+}
+
+# Lookup: Message-ID -> mail path. Store results in global $fname
+lookup_msgid () {
+    fname=$(mu find -f p m:"$1" | head -n 1)
+}
+
+dump_info () {
+    echo "fname: $fname"
+    echo "msgid: $msgid"
+}
+
+if [ -z "$1" -o "$1" = "-h" -o "$1" = "-help" -o "$1" = "--help" ] ; then
+    die_usage
+fi
+if (echo "$1" | grep -q /) && test -f "$1" ; then      # arg is a file
+    fname="$1"
+    msgid=$(egrep -i '^message-id:' "$fname" | cut -f 2 -d':' | sed 's/[ <>]//g')
+elif ! (echo "$1" | grep -q /) ; then  # arg is a Message-ID
+    msgid="$1"
+    lookup_msgid "$msgid"      # side-effect: set $fname
+fi
+# dump_info ; exit 3
+if ! dirname "$fname" | egrep -q '/(cur|new|tmp)$' ; then
+    echo "Path not pointing inside a maildir: $fname" 1>&2
+    exit 2
+fi
+maildir=$(dirname $(dirname "$fname"))
+
+if ! [ -d "$maildir" ] ; then
+    echo "Not a (mail)dir: $maildir" 1>&1
+    exit 2
+fi
+
+# UGLY HACK: without sleep, push keys do not reach mutt, I _guess_ that there
+# might be some terminal-related issue here, since also waiting for an input
+# with "read" similarly "solves" the problem
+sleep 0.1
+mutt_keys="$HIDE_SIDEBAR_CMD/~i$msgid\n\n"
+exec $MUTT $MUTT_FLAGS -f "$maildir/" -e "push $mutt_keys"
diff --git a/blog/posts/2010/02/integrating_Mutt_with_Org-mode/remember-mail b/blog/posts/2010/02/integrating_Mutt_with_Org-mode/remember-mail
new file mode 100644 (file)
index 0000000..9e13e75
--- /dev/null
@@ -0,0 +1,23 @@
+#!/usr/bin/perl -w
+#
+# Helper for mutt to remember mails in Emacs' Org mode
+#
+# Copyright: © 2009-2010 Stefano Zacchiroli <zack@upsilon.cc> 
+# License: GNU General Public License (GPL), version 3 or above
+#
+# Example of mutt macro to invoke this hitting ESC-R (to be put in ~/.muttrc):
+#  macro index \eR "|~/bin/remember-mail\n"
+
+use strict;
+use Mail::Internet;
+use URI::Escape;
+
+my $msg = Mail::Internet->new(\*STDIN);
+$msg->head->get('message-id') =~ /^<(.*)>$/;
+my $mid = $1;
+my $subject = $msg->head->get('subject') || "";
+my $from = $msg->head->get('from') || "";
+chomp ($subject, $from);
+my $note_body = uri_escape("  Subject: $subject\n    From: $from");
+
+exec "emacsclient", "-t", "org-protocol:/remember:/m/mutt:$mid/mail/$note_body";