notmuch.c               \
        notmuch-dump.c          \
        notmuch-new.c           \
+       notmuch-reply.c         \
        notmuch-restore.c       \
        notmuch-search.c        \
        notmuch-setup.c         \
        notmuch-tag.c           \
        notmuch-time.c          \
        add-files.c             \
-       query-string.c
+       gmime-filter-reply.c    \
+       query-string.c          \
+       show-message.c
 
 notmuch_client_modules = $(notmuch_client_srcs:.c=.o)
 notmuch: $(notmuch_client_modules) lib/notmuch.a
 
--- /dev/null
+/*
+ * Copyright © 2009 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include "gmime-filter-reply.h"
+
+/**
+ * SECTION: gmime-filter-reply
+ * @title: GMimeFilterReply
+ * @short_description: Add/remove reply markers
+ *
+ * A #GMimeFilter for adding or removing reply markers
+ **/
+
+
+static void g_mime_filter_reply_class_init (GMimeFilterReplyClass *klass);
+static void g_mime_filter_reply_init (GMimeFilterReply *filter, GMimeFilterReplyClass *klass);
+static void g_mime_filter_reply_finalize (GObject *object);
+
+static GMimeFilter *filter_copy (GMimeFilter *filter);
+static void filter_filter (GMimeFilter *filter, char *in, size_t len, size_t prespace,
+                          char **out, size_t *outlen, size_t *outprespace);
+static void filter_complete (GMimeFilter *filter, char *in, size_t len, size_t prespace,
+                            char **out, size_t *outlen, size_t *outprespace);
+static void filter_reset (GMimeFilter *filter);
+
+
+static GMimeFilterClass *parent_class = NULL;
+
+GType
+g_mime_filter_reply_get_type (void)
+{
+       static GType type = 0;
+
+       if (!type) {
+               static const GTypeInfo info = {
+                       sizeof (GMimeFilterReplyClass),
+                       NULL, /* base_class_init */
+                       NULL, /* base_class_finalize */
+                       (GClassInitFunc) g_mime_filter_reply_class_init,
+                       NULL, /* class_finalize */
+                       NULL, /* class_data */
+                       sizeof (GMimeFilterReply),
+                       0,    /* n_preallocs */
+                       (GInstanceInitFunc) g_mime_filter_reply_init,
+                       NULL    /* value_table */
+               };
+
+               type = g_type_register_static (GMIME_TYPE_FILTER, "GMimeFilterReply", &info, (GTypeFlags) 0);
+       }
+
+       return type;
+}
+
+
+static void
+g_mime_filter_reply_class_init (GMimeFilterReplyClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       GMimeFilterClass *filter_class = GMIME_FILTER_CLASS (klass);
+
+       parent_class = (GMimeFilterClass *) g_type_class_ref (GMIME_TYPE_FILTER);
+
+       object_class->finalize = g_mime_filter_reply_finalize;
+
+       filter_class->copy = filter_copy;
+       filter_class->filter = filter_filter;
+       filter_class->complete = filter_complete;
+       filter_class->reset = filter_reset;
+}
+
+static void
+g_mime_filter_reply_init (GMimeFilterReply *filter, GMimeFilterReplyClass *klass)
+{
+       (void) klass;
+       filter->saw_nl = TRUE;
+       filter->saw_angle = FALSE;
+}
+
+static void
+g_mime_filter_reply_finalize (GObject *object)
+{
+       G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static GMimeFilter *
+filter_copy (GMimeFilter *filter)
+{
+       GMimeFilterReply *reply = (GMimeFilterReply *) filter;
+
+       return g_mime_filter_reply_new (reply->encode);
+}
+
+static void
+filter_filter (GMimeFilter *filter, char *inbuf, size_t inlen, size_t prespace,
+              char **outbuf, size_t *outlen, size_t *outprespace)
+{
+       GMimeFilterReply *reply = (GMimeFilterReply *) filter;
+       register const char *inptr = inbuf;
+       const char *inend = inbuf + inlen;
+       char *outptr;
+
+       (void) prespace;
+       if (reply->encode) {
+               g_mime_filter_set_size (filter, 3 * inlen, FALSE);
+
+               outptr = filter->outbuf;
+               while (inptr < inend) {
+                       if (reply->saw_nl) {
+                               *outptr++ = '>';
+                               *outptr++ = ' ';
+                               reply->saw_nl = FALSE;
+                       }
+                       if (*inptr == '\n')
+                               reply->saw_nl = TRUE;
+                       else
+                               reply->saw_nl = FALSE;
+
+                       *outptr++ = *inptr++;
+               }
+       } else {
+               g_mime_filter_set_size (filter, inlen + 1, FALSE);
+
+               outptr = filter->outbuf;
+               while (inptr < inend) {
+                       if (reply->saw_nl) {
+                               if (*inptr == '>')
+                                       reply->saw_angle = TRUE;
+                               else
+                                       *outptr++ = *inptr;
+                               reply->saw_nl = FALSE;
+                       } else if (reply->saw_angle) {
+                               if (*inptr == ' ')
+                                       ;
+                               else
+                                       *outptr++ = *inptr;
+                               reply->saw_angle = FALSE;
+                       } else {
+                               if (*inptr == '\n')
+                                       reply->saw_nl = TRUE;
+                               *outptr++ = *inptr;
+                       }
+
+                       inptr++;
+               }
+       }
+
+       *outlen = outptr - filter->outbuf;
+       *outprespace = filter->outpre;
+       *outbuf = filter->outbuf;
+}
+
+static void
+filter_complete (GMimeFilter *filter, char *inbuf, size_t inlen, size_t prespace,
+                char **outbuf, size_t *outlen, size_t *outprespace)
+{
+       if (inbuf && inlen)
+               filter_filter (filter, inbuf, inlen, prespace, outbuf, outlen, outprespace);
+}
+
+static void
+filter_reset (GMimeFilter *filter)
+{
+       GMimeFilterReply *reply = (GMimeFilterReply *) filter;
+
+       reply->saw_nl = TRUE;
+       reply->saw_angle = FALSE;
+}
+
+
+/**
+ * g_mime_filter_reply_new:
+ * @encode: %TRUE if the filter should encode or %FALSE otherwise
+ * @dots: encode/decode dots (as for SMTP)
+ *
+ * Creates a new #GMimeFilterReply filter.
+ *
+ * If @encode is %TRUE, then all lines will be prefixed by "> ",
+ * otherwise any lines starting with "> " will have that removed
+ *
+ * Returns: a new #GMimeFilterReply filter.
+ **/
+GMimeFilter *
+g_mime_filter_reply_new (gboolean encode)
+{
+       GMimeFilterReply *new_reply;
+
+       new_reply = (GMimeFilterReply *) g_object_newv (GMIME_TYPE_FILTER_REPLY, 0, NULL);
+       new_reply->encode = encode;
+
+       return (GMimeFilter *) new_reply;
+}
+
 
--- /dev/null
+/*
+ * Copyright © 2009 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#ifndef _GMIME_FILTER_REPLY_H_
+#define _GMIME_FILTER_REPLY_H_
+
+#include <gmime/gmime-filter.h>
+
+G_BEGIN_DECLS
+
+#define GMIME_TYPE_FILTER_REPLY            (g_mime_filter_reply_get_type ())
+#define GMIME_FILTER_REPLY(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GMIME_TYPE_FILTER_REPLY, GMimeFilterReply))
+#define GMIME_FILTER_REPLY_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GMIME_TYPE_FILTER_REPLY, GMimeFilterReplyClass))
+#define GMIME_IS_FILTER_REPLY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GMIME_TYPE_FILTER_REPLY))
+#define GMIME_IS_FILTER_REPLY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GMIME_TYPE_FILTER_REPLY))
+#define GMIME_FILTER_REPLY_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GMIME_TYPE_FILTER_REPLY, GMimeFilterReplyClass))
+
+typedef struct _GMimeFilterReply GMimeFilterReply;
+typedef struct _GMimeFilterReplyClass GMimeFilterReplyClass;
+
+/**
+ * GMimeFilterReply:
+ * @parent_object: parent #GMimeFilter
+ * @encode: encoding vs decoding reply markers
+ * @saw_nl: previous char was a \n
+ * @saw_angle: previous char was a >
+ *
+ * A filter to insert/remove reply markers (lines begining with >)
+ **/
+struct _GMimeFilterReply {
+       GMimeFilter parent_object;
+
+       gboolean encode;
+       gboolean saw_nl;
+       gboolean saw_angle;
+};
+
+struct _GMimeFilterReplyClass {
+       GMimeFilterClass parent_class;
+
+};
+
+
+GType g_mime_filter_reply_get_type (void);
+
+GMimeFilter *g_mime_filter_reply_new (gboolean encode);
+
+G_END_DECLS
+
+
+#endif /* _GMIME_FILTER_REPLY_H_ */
 
 int
 notmuch_new_command (void *ctx, int argc, char *argv[]);
 
+int
+notmuch_reply_command (void *ctx, int argc, char *argv[]);
+
 int
 notmuch_restore_command (void *ctx, int argc, char *argv[]);
 
 int
 notmuch_tag_command (void *ctx, int argc, char *argv[]);
 
-notmuch_status_t
-add_files (notmuch_database_t *notmuch, const char *path,
-          add_files_state_t *state);
-
-char *
-query_string_from_args (void *ctx, int argc, char *argv[]);
-
 const char *
 notmuch_time_relative_date (void *ctx, time_t then);
 
 double
 notmuch_time_elapsed (struct timeval start, struct timeval end);
 
+notmuch_status_t
+add_files (notmuch_database_t *notmuch, const char *path,
+          add_files_state_t *state);
+
+char *
+query_string_from_args (void *ctx, int argc, char *argv[]);
+
+notmuch_status_t
+show_message_body (const char *filename,
+                  void (*show_part) (GMimeObject *part, int *part_count));
+
 #endif
 
--- /dev/null
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2009 Carl Worth
+ * Copyright © 2009 Keith Packard
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ *
+ * Authors: Carl Worth <cworth@cworth.org>
+ *         Keith Packard <keithp@keithp.com>
+ */
+
+#include "notmuch-client.h"
+#include "gmime-filter-reply.h"
+
+static void
+reply_part(GMimeObject *part, int *part_count)
+{
+    GMimeContentDisposition *disposition;
+    GMimeContentType *content_type;
+    GMimeDataWrapper *wrapper;
+
+    (void) part_count;
+    disposition = g_mime_object_get_content_disposition (part);
+    if (disposition &&
+       strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0)
+    {
+       const char *filename = g_mime_part_get_filename (GMIME_PART (part));
+       content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
+
+       printf ("Attachment: %s (%s)\n", filename,
+               g_mime_content_type_to_string (content_type));
+       return;
+    }
+
+    content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
+
+    if (g_mime_content_type_is_type (content_type, "text", "*") &&
+       !g_mime_content_type_is_type (content_type, "text", "html"))
+    {
+       GMimeStream *stream_stdout = NULL, *stream_filter = NULL;
+       stream_stdout = g_mime_stream_file_new (stdout);
+       if (stream_stdout) {
+           g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE);
+           stream_filter = g_mime_stream_filter_new(stream_stdout);
+       }
+       g_mime_stream_filter_add(GMIME_STREAM_FILTER(stream_filter),
+                                g_mime_filter_reply_new(TRUE));
+       wrapper = g_mime_part_get_content_object (GMIME_PART (part));
+       if (wrapper && stream_filter)
+           g_mime_data_wrapper_write_to_stream (wrapper, stream_filter);
+       if (stream_filter)
+           g_object_unref(stream_filter);
+       if (stream_stdout)
+           g_object_unref(stream_stdout);
+    }
+    else
+    {
+       printf ("Non-text part: %s\n",
+               g_mime_content_type_to_string (content_type));
+    }
+}
+
+int
+notmuch_reply_command (void *ctx, int argc, char *argv[])
+{
+    void *local = talloc_new (ctx);
+    char *query_string;
+    notmuch_database_t *notmuch = NULL;
+    notmuch_query_t *query = NULL;
+    notmuch_messages_t *messages;
+    notmuch_message_t *message;
+    int ret = 0;
+
+    const char *headers[] = {
+           "Subject", "From", "To", "Cc", "Bcc", "Date",
+           "In-Reply-To", "References"
+    };
+    const char *name, *value;
+    unsigned int i;
+
+    notmuch = notmuch_database_open (NULL);
+    if (notmuch == NULL) {
+       ret = 1;
+       goto DONE;
+    }
+
+    query_string = query_string_from_args (local, argc, argv);
+    if (query_string == NULL) {
+       fprintf (stderr, "Out of memory\n");
+       ret = 1;
+       goto DONE;
+    }
+
+    query = notmuch_query_create (notmuch, query_string);
+    if (query == NULL) {
+       fprintf (stderr, "Out of memory\n");
+       ret = 1;
+       goto DONE;
+    }
+
+    for (messages = notmuch_query_search_messages (query);
+        notmuch_messages_has_more (messages);
+        notmuch_messages_advance (messages))
+    {
+       message = notmuch_messages_get (messages);
+
+       for (i = 0; i < ARRAY_SIZE (headers); i++) {
+           name = headers[i];
+           value = notmuch_message_get_header (message, name);
+           if (value)
+               printf ("%s: %s\n", name, value);
+       }
+
+       show_message_body (notmuch_message_get_filename (message), reply_part);
+
+       notmuch_message_destroy (message);
+    }
+
+  DONE:
+    if (local)
+       talloc_free (local);
+
+    if (query)
+       notmuch_query_destroy (query);
+
+    if (notmuch)
+       notmuch_database_close (notmuch);
+
+    return ret;
+}
 
 }
 
 static void
-show_message_part (GMimeObject *part, int *part_count)
+show_part(GMimeObject *part, int *part_count)
 {
-    GMimeStream *stream;
-    GMimeDataWrapper *wrapper;
     GMimeContentDisposition *disposition;
     GMimeContentType *content_type;
-
-    *part_count = *part_count + 1;
-
-    if (GMIME_IS_MULTIPART (part)) {
-       GMimeMultipart *multipart = GMIME_MULTIPART (part);
-       int i;
-
-       for (i = 0; i < g_mime_multipart_get_count (multipart); i++) {
-           if (GMIME_IS_MULTIPART_SIGNED (multipart)) {
-               /* Don't index the signature. */
-               if (i == 1)
-                   continue;
-               if (i > 1)
-                   fprintf (stderr, "Warning: Unexpected extra parts of mutlipart/signed. Continuing.\n");
-           }
-           show_message_part (g_mime_multipart_get_part (multipart, i),
-                              part_count);
-       }
-       return;
-    }
-
-    if (GMIME_IS_MESSAGE_PART (part)) {
-       GMimeMessage *mime_message;
-
-       mime_message = g_mime_message_part_get_message (GMIME_MESSAGE_PART (part));
-
-       show_message_part (g_mime_message_get_mime_part (mime_message),
-                          part_count);
-
-       return;
-    }
-
-    if (! (GMIME_IS_PART (part))) {
-       fprintf (stderr, "Warning: Not displaying unknown mime part: %s.\n",
-                g_type_name (G_OBJECT_TYPE (part)));
-       return;
-    }
+    GMimeDataWrapper *wrapper;
 
     disposition = g_mime_object_get_content_disposition (part);
     if (disposition &&
     if (g_mime_content_type_is_type (content_type, "text", "*") &&
        !g_mime_content_type_is_type (content_type, "text", "html"))
     {
-       stream = g_mime_stream_file_new (stdout);
+       GMimeStream *stream = g_mime_stream_file_new (stdout);
        g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream), FALSE);
 
        wrapper = g_mime_part_get_content_object (GMIME_PART (part));
-       if (wrapper)
+       if (wrapper && stream)
            g_mime_data_wrapper_write_to_stream (wrapper, stream);
-
-       g_object_unref (stream);
+       if (stream)
+           g_object_unref(stream);
     }
     else
     {
     printf ("\fpart}\n");
 }
 
-static notmuch_status_t
-show_message_body (const char *filename)
-{
-    GMimeStream *stream = NULL;
-    GMimeParser *parser = NULL;
-    GMimeMessage *mime_message = NULL;
-    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
-    static int initialized = 0;
-    FILE *file = NULL;
-    int part_count = 0;
-
-    if (! initialized) {
-       g_mime_init (0);
-       initialized = 1;
-    }
-
-    file = fopen (filename, "r");
-    if (! file) {
-       fprintf (stderr, "Error opening %s: %s\n", filename, strerror (errno));
-       ret = NOTMUCH_STATUS_FILE_ERROR;
-       goto DONE;
-    }
-
-    stream = g_mime_stream_file_new (file);
-    g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream), FALSE);
-
-    parser = g_mime_parser_new_with_stream (stream);
-
-    mime_message = g_mime_parser_construct_message (parser);
-
-    show_message_part (g_mime_message_get_mime_part (mime_message),
-                      &part_count);
-
-  DONE:
-    if (mime_message)
-       g_object_unref (mime_message);
-
-    if (parser)
-       g_object_unref (parser);
-
-    if (stream)
-       g_object_unref (stream);
-
-    if (file)
-       fclose (file);
-
-    return ret;
-}
-
 int
 notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
 {
        printf ("\fheader}\n");
        printf ("\fbody{\n");
 
-       show_message_body (notmuch_message_get_filename (message));
+       show_message_body (notmuch_message_get_filename (message), show_part);
 
        printf ("\fbody}\n");
 
 
 operators, but will have to be protected from interpretation by the
 shell, (such as by putting quotation marks around any parenthesized
 expression).
+.TP
+.BR reply " <search-term>..."
+
+Constructs a reply template for a set of messages.
+
+See the documentation of
+.B search
+for deatils of the supported syntax of search terms.
+
+To make replying to email easier,
+.B notmuch reply
+takes an existing set of messages and constructs a suitable mail
+template, taking From: and To: messages and using those for the new
+To: address; copying Cc: addresses, building a suitable new subject
+including Re: at the front, adding the old message IDs to the
+References list and setting the In-Reply-To: field correctly.
+
+The resulting message template is output to stdout.
+
 .TP
 .BR show " <search-term>..."
 
 
 /* notmuch - Not much of an email program, (just index and search)
  *
  * Copyright © 2009 Carl Worth
+ * Copyright © 2009 Keith Packard
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see http://www.gnu.org/licenses/ .
  *
- * Author: Carl Worth <cworth@cworth.org>
+ * Authors: Carl Worth <cworth@cworth.org>
+ *         Keith Packard <keithp@keithp.com>
  */
 
 #include "notmuch-client.h"
       "\t\tthe Boolean operators, but will have to be protected from\n"
       "\t\tinterpretation by the shell, (such as by putting quotation\n"
       "\t\tmarks around any parenthesized expression)." },
+    { "reply", notmuch_reply_command,
+      "<search-terms> [...]\n\n"
+      "\t\tFormats a reply from a set of existing messages.",
+      "\t\tConstructs a new message as a reply to a set of existing\n"
+      "\t\tmessages. The From: address is used as a To: address\n"
+      "\t\talong with all old To: addresses. All of the Cc: addresses\n"
+      "\t\tare copied as new Cc: addresses. An In-Reply-To: header\n"
+      "\t\twill be constructed from the name and date of the original\n"
+      "\t\tmessage, and the original Message-ID will be added to the\n"
+      "\t\tlist of References in the new message. The text of each\n"
+      "\t\tmessage (as described in the \"show\" command) will be\n"
+      "\t\tpresented, each line prefixed with \"> \" The resulting\n"
+      "\t\tmessage will be dumped to stdout." },
     { "show", notmuch_show_command,
       "<search-terms> [...]\n\n"
       "\t\tShows all messages matching the search terms.",
 
--- /dev/null
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2009 Carl Worth
+ * Copyright © 2009 Keith Packard
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ *
+ * Authors: Carl Worth <cworth@cworth.org>
+ *         Keith Packard <keithp@keithp.com>
+ */
+
+#include "notmuch-client.h"
+
+static void
+show_message_part (GMimeObject *part, int *part_count,
+                  void (*show_part) (GMimeObject *part, int *part_count))
+{
+    *part_count = *part_count + 1;
+
+    if (GMIME_IS_MULTIPART (part)) {
+       GMimeMultipart *multipart = GMIME_MULTIPART (part);
+       int i;
+
+       for (i = 0; i < g_mime_multipart_get_count (multipart); i++) {
+           if (GMIME_IS_MULTIPART_SIGNED (multipart)) {
+               /* Don't index the signature. */
+               if (i == 1)
+                   continue;
+               if (i > 1)
+                   fprintf (stderr, "Warning: Unexpected extra parts of mutlipart/signed. Continuing.\n");
+           }
+           show_message_part (g_mime_multipart_get_part (multipart, i),
+                              part_count, show_part);
+       }
+       return;
+    }
+
+    if (GMIME_IS_MESSAGE_PART (part)) {
+       GMimeMessage *mime_message;
+
+       mime_message = g_mime_message_part_get_message (GMIME_MESSAGE_PART (part));
+
+       show_message_part (g_mime_message_get_mime_part (mime_message),
+                          part_count, show_part);
+
+       return;
+    }
+
+    if (! (GMIME_IS_PART (part))) {
+       fprintf (stderr, "Warning: Not displaying unknown mime part: %s.\n",
+                g_type_name (G_OBJECT_TYPE (part)));
+       return;
+    }
+
+    (*show_part) (part, part_count);
+}
+
+notmuch_status_t
+show_message_body (const char *filename,
+                  void (*show_part) (GMimeObject *part, int *part_count))
+{
+    GMimeStream *stream = NULL;
+    GMimeParser *parser = NULL;
+    GMimeMessage *mime_message = NULL;
+    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+    static int initialized = 0;
+    FILE *file = NULL;
+    int part_count = 0;
+
+    if (! initialized) {
+       g_mime_init (0);
+       initialized = 1;
+    }
+
+    file = fopen (filename, "r");
+    if (! file) {
+       fprintf (stderr, "Error opening %s: %s\n", filename, strerror (errno));
+       ret = NOTMUCH_STATUS_FILE_ERROR;
+       goto DONE;
+    }
+
+    stream = g_mime_stream_file_new (file);
+    g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream), FALSE);
+
+    parser = g_mime_parser_new_with_stream (stream);
+
+    mime_message = g_mime_parser_construct_message (parser);
+
+    show_message_part (g_mime_message_get_mime_part (mime_message),
+                      &part_count, show_part);
+
+  DONE:
+    if (mime_message)
+       g_object_unref (mime_message);
+
+    if (parser)
+       g_object_unref (parser);
+
+    if (stream)
+       g_object_unref (stream);
+
+    if (file)
+       fclose (file);
+
+    return ret;
+}