Re: [PATCH] Omit User-Agent: header by default
[notmuch-archives.git] / 7f / 124f5170f9ecc85f79b4f2ba71de6627462e88
1 Return-Path: <m.walters@qmul.ac.uk>\r
2 X-Original-To: notmuch@notmuchmail.org\r
3 Delivered-To: notmuch@notmuchmail.org\r
4 Received: from localhost (localhost [127.0.0.1])\r
5         by olra.theworths.org (Postfix) with ESMTP id 9DA8C431FAF\r
6         for <notmuch@notmuchmail.org>; Sun,  5 Feb 2012 03:49:11 -0800 (PST)\r
7 X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
8 X-Spam-Flag: NO\r
9 X-Spam-Score: -1.098\r
10 X-Spam-Level: \r
11 X-Spam-Status: No, score=-1.098 tagged_above=-999 required=5\r
12         tests=[DKIM_ADSP_CUSTOM_MED=0.001, FREEMAIL_FROM=0.001,\r
13         NML_ADSP_CUSTOM_MED=1.2, RCVD_IN_DNSWL_MED=-2.3] autolearn=disabled\r
14 Received: from olra.theworths.org ([127.0.0.1])\r
15         by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
16         with ESMTP id U6gucuK9F16g for <notmuch@notmuchmail.org>;\r
17         Sun,  5 Feb 2012 03:49:10 -0800 (PST)\r
18 Received: from mail2.qmul.ac.uk (mail2.qmul.ac.uk [138.37.6.6])\r
19         (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))\r
20         (No client certificate requested)\r
21         by olra.theworths.org (Postfix) with ESMTPS id E4651431FAE\r
22         for <notmuch@notmuchmail.org>; Sun,  5 Feb 2012 03:49:09 -0800 (PST)\r
23 Received: from smtp.qmul.ac.uk ([138.37.6.40])\r
24         by mail2.qmul.ac.uk with esmtp (Exim 4.71)\r
25         (envelope-from <m.walters@qmul.ac.uk>)\r
26         id 1Ru0ad-0007P8-Nh; Sun, 05 Feb 2012 11:49:06 +0000\r
27 Received: from 94-192-233-223.zone6.bethere.co.uk ([94.192.233.223]\r
28         helo=localhost)\r
29         by smtp.qmul.ac.uk with esmtpsa (TLSv1:AES128-SHA:128) (Exim 4.69)\r
30         (envelope-from <m.walters@qmul.ac.uk>)\r
31         id 1Ru0ac-0001Gu-RZ; Sun, 05 Feb 2012 11:49:03 +0000\r
32 From: Mark Walters <markwalters1009@gmail.com>\r
33 To: Adam Wolfe Gordon <awg+notmuch@xvx.ca>, notmuch@notmuchmail.org\r
34 Subject: Re: [PATCH v3 2/5] reply: Add a JSON reply format.\r
35 In-Reply-To: <1326995217-27423-3-git-send-email-awg+notmuch@xvx.ca>\r
36 References: <1326995217-27423-1-git-send-email-awg+notmuch@xvx.ca>\r
37         <1326995217-27423-3-git-send-email-awg+notmuch@xvx.ca>\r
38 User-Agent: Notmuch/0.11+154~ged6d37e (http://notmuchmail.org) Emacs/23.2.1\r
39         (i486-pc-linux-gnu)\r
40 Date: Sun, 05 Feb 2012 11:50:12 +0000\r
41 Message-ID: <87mx8xpki3.fsf@qmul.ac.uk>\r
42 MIME-Version: 1.0\r
43 Content-Type: text/plain; charset=us-ascii\r
44 X-Sender-Host-Address: 94.192.233.223\r
45 X-QM-SPAM-Info: Sender has good ham record.  :)\r
46 X-QM-Body-MD5: 894b95a91825f7f31952e25cbefcec99 (of first 20000 bytes)\r
47 X-SpamAssassin-Score: -1.8\r
48 X-SpamAssassin-SpamBar: -\r
49 X-SpamAssassin-Report: The QM spam filters have analysed this message to\r
50         determine if it is\r
51         spam. We require at least 5.0 points to mark a message as spam.\r
52         This message scored -1.8 points.\r
53         Summary of the scoring: \r
54         * -2.3 RCVD_IN_DNSWL_MED RBL: Sender listed at http://www.dnswl.org/,\r
55         *      medium trust\r
56         *      [138.37.6.40 listed in list.dnswl.org]\r
57         * 0.0 FREEMAIL_FROM Sender email is commonly abused enduser mail\r
58         provider *      (markwalters1009[at]gmail.com)\r
59         * -0.0 T_RP_MATCHES_RCVD Envelope sender domain matches handover relay\r
60         *      domain\r
61         *  0.5 AWL AWL: From: address is in the auto white-list\r
62 X-QM-Scan-Virus: ClamAV says the message is clean\r
63 X-BeenThere: notmuch@notmuchmail.org\r
64 X-Mailman-Version: 2.1.13\r
65 Precedence: list\r
66 List-Id: "Use and development of the notmuch mail system."\r
67         <notmuch.notmuchmail.org>\r
68 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
69         <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
70 List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
71 List-Post: <mailto:notmuch@notmuchmail.org>\r
72 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
73 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
74         <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
75 X-List-Received-Date: Sun, 05 Feb 2012 11:49:11 -0000\r
76 \r
77 On Thu, 19 Jan 2012 10:46:54 -0700, Adam Wolfe Gordon <awg+notmuch@xvx.ca> wrote:\r
78 > This new JSON format for replies includes headers generated for a reply\r
79 > message as well as the headers and all text parts of the original message.\r
80 > Using this data, a client can intelligently create a reply. For example,\r
81 > the emacs client will be able to create replies with quoted HTML parts by\r
82 > parsing the HTML parts using w3m.\r
83 \r
84 Hi this is only a preliminary look so far as I read the code. Note this\r
85 is the first time I have tried reviewing a substantial chunk of code so\r
86 sorry for any stupidities on my part!\r
87 \r
88 Best wishes\r
89 \r
90 Mark\r
91 \r
92 >  notmuch-reply.c |  271 +++++++++++++++++++++++++++++++++++++++++++++++--------\r
93 >  1 files changed, 231 insertions(+), 40 deletions(-)\r
94\r
95 > diff --git a/notmuch-reply.c b/notmuch-reply.c\r
96 > index 0f682db..b4c2426 100644\r
97 > --- a/notmuch-reply.c\r
98 > +++ b/notmuch-reply.c\r
99 > @@ -30,6 +30,15 @@ reply_headers_message_part (GMimeMessage *message);\r
100 >  static void\r
101 >  reply_part_content (GMimeObject *part);\r
102 >  \r
103 > +static void\r
104 > +reply_part_start_json (GMimeObject *part, int *part_count);\r
105 > +\r
106 > +static void\r
107 > +reply_part_content_json (GMimeObject *part);\r
108 > +\r
109 > +static void\r
110 > +reply_part_end_json (GMimeObject *part);\r
111 > +\r
112 >  static const notmuch_show_format_t format_reply = {\r
113 >      "",\r
114 >       "", NULL,\r
115 > @@ -46,6 +55,22 @@ static const notmuch_show_format_t format_reply = {\r
116 >      ""\r
117 >  };\r
118 >  \r
119 > +static const notmuch_show_format_t format_json = {\r
120 > +    "",\r
121 > +     "", NULL,\r
122 > +         "", NULL, NULL, "",\r
123 > +         "",\r
124 > +             reply_part_start_json,\r
125 > +             NULL,\r
126 > +             NULL,\r
127 > +             reply_part_content_json,\r
128 > +             reply_part_end_json,\r
129 > +             "",\r
130 > +         "",\r
131 > +     "", "",\r
132 > +    ""\r
133 > +};\r
134 > +\r
135 >  static void\r
136 >  show_reply_headers (GMimeMessage *message)\r
137 >  {\r
138 > @@ -86,6 +111,17 @@ reply_headers_message_part (GMimeMessage *message)\r
139 >      printf ("> Date: %s\n", g_mime_message_get_date_as_string (message));\r
140 >  }\r
141 >  \r
142 > +static notmuch_bool_t\r
143 > +reply_check_part_type (GMimeObject *part, const char *type, const char *subtype,\r
144 > +                    const char *disposition)\r
145 > +{\r
146 > +    GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));\r
147 > +    GMimeContentDisposition *part_disposition = g_mime_object_get_content_disposition (part);\r
148 > +\r
149 > +    return (g_mime_content_type_is_type (content_type, type, subtype) &&\r
150 > +         (!part_disposition ||\r
151 > +          strcmp (part_disposition->disposition, disposition) == 0));\r
152 > +}\r
153 >  \r
154 >  static void\r
155 >  reply_part_content (GMimeObject *part)\r
156 > @@ -147,6 +183,63 @@ reply_part_content (GMimeObject *part)\r
157 >      }\r
158 >  }\r
159 >  \r
160 > +static void\r
161 > +reply_part_start_json (GMimeObject *part, unused (int *part_count))\r
162 > +{\r
163 > +    if (reply_check_part_type (part, "text", "*", GMIME_DISPOSITION_INLINE))\r
164 > +     printf ("{ ");\r
165 > +}\r
166 > +\r
167 > +static void\r
168 > +reply_part_end_json (GMimeObject *part)\r
169 > +{\r
170 > +    if (reply_check_part_type (part, "text", "*", GMIME_DISPOSITION_INLINE))\r
171 > +     printf ("}, ");\r
172 > +}\r
173 > +\r
174 > +static void\r
175 > +reply_part_content_json (GMimeObject *part)\r
176 > +{\r
177 > +    GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));\r
178 > +    void *ctx = talloc_new (NULL);\r
179 > +\r
180 > +    /* We only care about inline text parts for reply purposes */\r
181 > +    if (reply_check_part_type (part, "text", "*", GMIME_DISPOSITION_INLINE)) {\r
182 \r
183 This seems to be different from the logic in the text output: I think\r
184 that inlines all text/* regardless of disposition. I think the JSON\r
185 output should include at least as much as the text output as it is easy\r
186 for the caller to discard parts.\r
187 \r
188 > +     GMimeDataWrapper *wrapper;\r
189 > +     GByteArray *part_content;\r
190 > +\r
191 > +     printf ("\"content-type\": %s, \"content\": ",\r
192 > +            json_quote_str (ctx, g_mime_content_type_to_string (content_type)));\r
193 > +\r
194 > +     wrapper = g_mime_part_get_content_object (GMIME_PART (part));\r
195 > +     if (wrapper) {\r
196 > +         const char *charset = g_mime_object_get_content_type_parameter (part, "charset");\r
197 > +         GMimeStream *stream_memory = g_mime_stream_mem_new ();\r
198 > +         if (stream_memory) {\r
199 > +             GMimeStream *stream_filter = NULL;\r
200 > +             stream_filter = g_mime_stream_filter_new (stream_memory);\r
201 \r
202 > +             if (charset) {\r
203 > +                 g_mime_stream_filter_add (GMIME_STREAM_FILTER (stream_filter),\r
204 > +                                           g_mime_filter_charset_new (charset, "UTF-8"));\r
205 > +             }\r
206 > +\r
207 > +             if (stream_filter) {\r
208 \r
209 should the if (charset) block be inside the if (stream_filter) block?\r
210 \r
211 > +                 g_mime_data_wrapper_write_to_stream (wrapper, stream_filter);\r
212 > +                 part_content = g_mime_stream_mem_get_byte_array (GMIME_STREAM_MEM (stream_memory));\r
213 > +\r
214 > +                 printf ("%s", json_quote_chararray (ctx, (char *) part_content->data, part_content->len));\r
215 > +                 g_object_unref (stream_filter);\r
216 > +             }\r
217 > +         }\r
218 > +\r
219 > +         if (stream_memory)\r
220 > +             g_object_unref (stream_memory);\r
221 > +     }\r
222 > +    }\r
223 > +\r
224 > +    talloc_free (ctx);\r
225 \r
226 Does wrapper need to a free/unref somewhere?\r
227 \r
228 > +}\r
229 > +\r
230 >  /* Is the given address configured as one of the user's "personal" or\r
231 >   * "other" addresses. */\r
232 >  static int\r
233 > @@ -505,6 +598,61 @@ guess_from_received_header (notmuch_config_t *config, notmuch_message_t *message\r
234 >      return NULL;\r
235 >  }\r
236 >  \r
237 > +static GMimeMessage *\r
238 > +create_reply_message(void *ctx,\r
239 > +                  notmuch_config_t *config,\r
240 > +                  notmuch_message_t *message,\r
241 > +                  notmuch_bool_t reply_all)\r
242 > +{\r
243 > +    const char *subject, *from_addr = NULL;\r
244 > +    const char *in_reply_to, *orig_references, *references;\r
245 > +\r
246 > +    /* The 1 means we want headers in a "pretty" order. */\r
247 > +    GMimeMessage *reply = g_mime_message_new (1);\r
248 > +    if (reply == NULL) {\r
249 > +     fprintf (stderr, "Out of memory\n");\r
250 > +     return NULL;\r
251 > +    }\r
252 > +\r
253 > +    subject = notmuch_message_get_header (message, "subject");\r
254 > +    if (subject) {\r
255 > +     if (strncasecmp (subject, "Re:", 3))\r
256 > +         subject = talloc_asprintf (ctx, "Re: %s", subject);\r
257 > +     g_mime_message_set_subject (reply, subject);\r
258 > +    }\r
259 > +\r
260 > +    from_addr = add_recipients_from_message (reply, config,\r
261 > +                                          message, reply_all);\r
262 > +\r
263 > +    if (from_addr == NULL)\r
264 > +     from_addr = guess_from_received_header (config, message);\r
265 > +\r
266 > +    if (from_addr == NULL)\r
267 > +     from_addr = notmuch_config_get_user_primary_email (config);\r
268 > +\r
269 > +    from_addr = talloc_asprintf (ctx, "%s <%s>",\r
270 > +                              notmuch_config_get_user_name (config),\r
271 > +                              from_addr);\r
272 > +    g_mime_object_set_header (GMIME_OBJECT (reply),\r
273 > +                           "From", from_addr);\r
274 > +\r
275 > +    in_reply_to = talloc_asprintf (ctx, "<%s>",\r
276 > +                                notmuch_message_get_message_id (message));\r
277 > +\r
278 > +    g_mime_object_set_header (GMIME_OBJECT (reply),\r
279 > +                           "In-Reply-To", in_reply_to);\r
280 > +\r
281 > +    orig_references = notmuch_message_get_header (message, "references");\r
282 > +    references = talloc_asprintf (ctx, "%s%s%s",\r
283 > +                               orig_references ? orig_references : "",\r
284 > +                               orig_references ? " " : "",\r
285 > +                               in_reply_to);\r
286 > +    g_mime_object_set_header (GMIME_OBJECT (reply),\r
287 > +                           "References", references);\r
288 > +\r
289 > +    return reply;\r
290 > +}\r
291 > +\r
292 >  static int\r
293 >  notmuch_reply_format_default(void *ctx,\r
294 >                            notmuch_config_t *config,\r
295 > @@ -515,8 +663,6 @@ notmuch_reply_format_default(void *ctx,\r
296 >      GMimeMessage *reply;\r
297 >      notmuch_messages_t *messages;\r
298 >      notmuch_message_t *message;\r
299 > -    const char *subject, *from_addr = NULL;\r
300 > -    const char *in_reply_to, *orig_references, *references;\r
301 >      const notmuch_show_format_t *format = &format_reply;\r
302 >  \r
303 >      for (messages = notmuch_query_search_messages (query);\r
304 > @@ -525,62 +671,103 @@ notmuch_reply_format_default(void *ctx,\r
305 >      {\r
306 >       message = notmuch_messages_get (messages);\r
307 >  \r
308 > -     /* The 1 means we want headers in a "pretty" order. */\r
309 > -     reply = g_mime_message_new (1);\r
310 > -     if (reply == NULL) {\r
311 > -         fprintf (stderr, "Out of memory\n");\r
312 > -         return 1;\r
313 > -     }\r
314 > +     reply = create_reply_message (ctx, config, message, reply_all);\r
315 >  \r
316 > -     subject = notmuch_message_get_header (message, "subject");\r
317 > -     if (subject) {\r
318 > -         if (strncasecmp (subject, "Re:", 3))\r
319 > -             subject = talloc_asprintf (ctx, "Re: %s", subject);\r
320 > -         g_mime_message_set_subject (reply, subject);\r
321 > -     }\r
322 > +     if (!reply)\r
323 > +         continue;\r
324 >  \r
325 > -     from_addr = add_recipients_from_message (reply, config, message,\r
326 > -                                              reply_all);\r
327 > +     show_reply_headers (reply);\r
328 >  \r
329 > -     if (from_addr == NULL)\r
330 > -         from_addr = guess_from_received_header (config, message);\r
331 > +     g_object_unref (G_OBJECT (reply));\r
332 > +     reply = NULL;\r
333 >  \r
334 > -     if (from_addr == NULL)\r
335 > -         from_addr = notmuch_config_get_user_primary_email (config);\r
336 > +     printf ("On %s, %s wrote:\n",\r
337 > +             notmuch_message_get_header (message, "date"),\r
338 > +             notmuch_message_get_header (message, "from"));\r
339 >  \r
340 > -     from_addr = talloc_asprintf (ctx, "%s <%s>",\r
341 > -                                  notmuch_config_get_user_name (config),\r
342 > -                                  from_addr);\r
343 > -     g_mime_object_set_header (GMIME_OBJECT (reply),\r
344 > -                               "From", from_addr);\r
345 > +     show_message_body (message, format, params);\r
346 >  \r
347 > -     in_reply_to = talloc_asprintf (ctx, "<%s>",\r
348 > -                          notmuch_message_get_message_id (message));\r
349 > +     notmuch_message_destroy (message);\r
350 > +    }\r
351 > +    return 0;\r
352 > +}\r
353 >  \r
354 > -     g_mime_object_set_header (GMIME_OBJECT (reply),\r
355 > -                               "In-Reply-To", in_reply_to);\r
356 > +static int\r
357 > +notmuch_reply_format_json(void *ctx,\r
358 > +                       notmuch_config_t *config,\r
359 > +                       notmuch_query_t *query,\r
360 > +                       unused (notmuch_show_params_t *params),\r
361 > +                       notmuch_bool_t reply_all)\r
362 > +{\r
363 > +    GMimeMessage *reply;\r
364 > +    notmuch_messages_t *messages;\r
365 > +    notmuch_message_t *message;\r
366 > +    const notmuch_show_format_t *format = &format_json;\r
367 >  \r
368 > -     orig_references = notmuch_message_get_header (message, "references");\r
369 > -     references = talloc_asprintf (ctx, "%s%s%s",\r
370 > -                                   orig_references ? orig_references : "",\r
371 > -                                   orig_references ? " " : "",\r
372 > -                                   in_reply_to);\r
373 > -     g_mime_object_set_header (GMIME_OBJECT (reply),\r
374 > -                               "References", references);\r
375 > +    const char *reply_headers[] = {"from", "to", "subject", "in-reply-to", "references"};\r
376 > +    const char *orig_headers[] = {"from", "to", "cc", "subject", "date", "in-reply-to", "references"};\r
377 > +    unsigned int hidx;\r
378 >  \r
379 > -     show_reply_headers (reply);\r
380 > +    /* Start array of reply objects */\r
381 > +    printf ("[");\r
382 > +\r
383 > +    for (messages = notmuch_query_search_messages (query);\r
384 > +      notmuch_messages_valid (messages);\r
385 > +      notmuch_messages_move_to_next (messages))\r
386 > +    {\r
387 > +     message = notmuch_messages_get (messages);\r
388 > +     reply = create_reply_message (ctx, config, message, reply_all);\r
389 > +     if (!reply)\r
390 > +         continue;\r
391 > +\r
392 > +     /* Start a reply object */\r
393 > +     printf ("{ \"reply\": { \"headers\": { ");\r
394 > +\r
395 > +     for (hidx = 0; hidx < ARRAY_SIZE (reply_headers); hidx++)\r
396 > +     {\r
397 \r
398 Nit: I think the preferred style is brace on the same line as the for loop.\r
399 \r
400 > +         if (hidx)\r
401 > +             printf (", ");\r
402 > +\r
403 > +         printf ("%s: %s", json_quote_str (ctx, reply_headers[hidx]),\r
404 > +                 json_quote_str (ctx, g_mime_object_get_header (GMIME_OBJECT (reply), reply_headers[hidx])));\r
405 > +     }\r
406 >  \r
407 >       g_object_unref (G_OBJECT (reply));\r
408 >       reply = NULL;\r
409 >  \r
410 > -     printf ("On %s, %s wrote:\n",\r
411 > -             notmuch_message_get_header (message, "date"),\r
412 > -             notmuch_message_get_header (message, "from"));\r
413 > +     /* Done the headers for the reply, which has no body parts */\r
414 > +     printf ("} }");\r
415 \r
416 If replying to multiple messages (such as a whole thread) you get\r
417 multiple sets of "new headers". I think that probably is not what is\r
418 wanted but its still better than the weird things the text version\r
419 does. Might be worth putting a comment. [What I think should happen is\r
420 that a union of all the headers from all these is taken throwing away\r
421 duplicate addresses but that is obviously not part of this patch set]\r
422 \r
423 > +     /* Start the original */\r
424 > +     printf (", \"original\": { \"headers\": { ");\r
425 > +\r
426 > +     for (hidx = 0; hidx < ARRAY_SIZE (orig_headers); hidx++)\r
427 > +     {\r
428 > +         if (hidx)\r
429 > +             printf (", ");\r
430 > +\r
431 > +         printf ("%s: %s", json_quote_str (ctx, orig_headers[hidx]),\r
432 > +                 json_quote_str (ctx, notmuch_message_get_header (message, orig_headers[hidx])));\r
433 > +     }\r
434 > +\r
435 > +     /* End headers */\r
436 > +     printf (" }, \"body\": [ ");\r
437 >  \r
438 > +     /* Show body parts */\r
439 >       show_message_body (message, format, params);\r
440 >  \r
441 >       notmuch_message_destroy (message);\r
442 > +\r
443 > +     /* Done the original */\r
444 > +     printf ("{} ] }");\r
445 > +\r
446 > +     /* End the reply object. */\r
447 > +     printf (" }, ");\r
448 >      }\r
449 > +\r
450 > +    /* End array of reply objects */\r
451 > +    printf ("{} ]\n");\r
452 > +\r
453 >      return 0;\r
454 >  }\r
455 >  \r
456 > @@ -646,6 +833,7 @@ notmuch_reply_format_headers_only(void *ctx,\r
457 >  \r
458 >  enum {\r
459 >      FORMAT_DEFAULT,\r
460 > +    FORMAT_JSON,\r
461 >      FORMAT_HEADERS_ONLY,\r
462 >  };\r
463 >  \r
464 > @@ -666,6 +854,7 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])\r
465 >      notmuch_opt_desc_t options[] = {\r
466 >       { NOTMUCH_OPT_KEYWORD, &format, "format", 'f',\r
467 >         (notmuch_keyword_t []){ { "default", FORMAT_DEFAULT },\r
468 > +                               { "json", FORMAT_JSON },\r
469 >                                 { "headers-only", FORMAT_HEADERS_ONLY },\r
470 >                                 { 0, 0 } } },\r
471 >       { NOTMUCH_OPT_KEYWORD, &reply_all, "reply-to", 'r',\r
472 > @@ -684,6 +873,8 @@ notmuch_reply_command (void *ctx, int argc, char *argv[])\r
473 >  \r
474 >      if (format == FORMAT_HEADERS_ONLY)\r
475 >       reply_format_func = notmuch_reply_format_headers_only;\r
476 > +    else if (format == FORMAT_JSON)\r
477 > +     reply_format_func = notmuch_reply_format_json;\r
478 >      else\r
479 >       reply_format_func = notmuch_reply_format_default;\r
480 >  \r
481 > -- \r
482 > 1.7.5.4\r
483\r
484 > _______________________________________________\r
485 > notmuch mailing list\r
486 > notmuch@notmuchmail.org\r
487 > http://notmuchmail.org/mailman/listinfo/notmuch\r