Category Archives: gnus

Publishing Emacs News as plain text, HTML, and attached Org file

Update 2016-02-05: Since @ThierryStoehr linked to this post about Emacs News-related code, I figured I'd add a link to the other support functions I've been using to help me with Emacs News summarization. There's also this bit:
(let ((date (org-read-date nil nil "-mon")))
    (concat
     (my/org-list-from-rss "http://planet.emacsen.org/atom.xml" date) "\n"
     (shell-command-to-string (concat "~/bin/list-reddit-links.coffee emacs " date)) "\n"
     (shell-command-to-string (concat "~/bin/list-reddit-links.coffee org-mode " date)) "\n"
     "- New packages:\n"
     (my/list-new-packages) 
     "\n"))
Handy little things! ------ I've been publishing these weekly summaries of Emacs-related links on my blog and to the emacs-tangents mailing list / newsgroup. I started by posting plain text from Org Mode's ASCII export, and people asked for Org Mode and HTML formats. So here's some code that prepares things for pasting into a Gnus message buffer. It turns out that order matters for multipart/alternative - start with plain text, then include richer alternatives. First time around, I put the HTML version first, so people didn't end up seeing it. Anyway, here's something that shows up properly now: text/plain, then text/html, with text/x-org attached. The heavy lifting is done with org-export-string-as, which exports into different formats.
  (defun my/share-emacs-news ()
    "Prepare current subtree for yanking into post."
    (interactive)
    ;; Draft Gnus article
    (save-restriction
      (org-narrow-to-subtree)
      (let ((org-export-html-preamble nil)
            (org-html-toplevel-hlevel 3)
            output)
        (setq output
              (apply
               'format
               "<#multipart type=alternative>
<#part type=\"text/plain\" disposition=inline>
%s
<#/part>
<#part type=\"text/html\" disposition=inline>
%s
<#/part>
<#/multipart>
<#part type=\"text/x-org\" disposition=attachment name=\"emacs-news.org\">
%s
<#/part>
"
               (mapcar
                (lambda (format)
                  (org-export-string-as (buffer-substring (point-min) (point-max)) format t))
                '(ascii html org))))
        (kill-new output))))
Howard Abrams showed me something like this in June 2015's Emacs Hangout (~1:18:26) using org-mime-org-buffer-htmlize, which probably does the job in a much cooler way. =) I thought he had a blog post about it, but I can't seem to find it. Anyway, there's my little hack above!

Mail with Gnus on Windows

Update 2015-11-26: fixed link to my config. Thanks, Thomas! I use Gmail for my mail because it:
  • synchronizes with my phone, which is handy for notifications and quick replies
  • filters most of the spam for me
  • works with a few interesting extensions such as Boomerang for Gmail
However, I like the way the Gnus mail/news client in Emacs gives me a much more keyboard-friendly way to manage lots of mail, and I can even write code to partially automate some of my common operations. I used to have my config in in ~/.gnus, but people might find it handy, so I've added it to my public Emacs configuration. I like using Gmane to read mailing lists, and I use IMAP to read my Gmail.
(setq gnus-select-method '(nnnil ""))
(setq gnus-secondary-select-methods
      '((nntp "news.gmane.org")
        (nnimap "imap.gmail.com"
                (nnimap-address "imap.gmail.com")
                (nnimap-server-port 993)
                (nnimap-stream ssl)
                (nnimap-authenticator login))))
I have two-factor authentication enabled for Gmail, so I set up an app-specific password for Gnus. I have an ~/.authinfo file set up with something like:
machine imap.gmail.com login [email protected] password mysecretapppassword
machine imap.gmail.com login [email protected] password mysecretapppassword port 993
machine smtp.gmail.com login [email protected] password mysecretapppassword port 587
(I should probably get around to using GPG to automatically encrypt and decrypt this file.) Sending e-mail on Windows was a bit of a pain. Fortunately, I eventually found something that works. I've configured emailrelay to accept the mail and forward it to Gmail. The server starts with this batch file:
start "emailrelay" "C:\Program Files (x86)\emailrelay\emailrelay.exe" --as-proxy smtp.gmail.com:25 --client-auth "C:/sacha/.emailrelay" --client-tls --log --pid-file "C:\Program Files (x86)\emailrelay\emailrelay.pid" --spool-dir C:\sacha\tmp\emailrelay
Sending queued mail works with this batch file:
"c:\Program Files (x86)\emailrelay\emailrelay.exe" --as-client smtp.gmail.com:587 --client-auth c:\sacha\.emailrelay --client-tls --spool-dir c:\sacha\tmp\emailrelay
I should probably get around to using --as-proxy properly, since it still seems to hold mail until I explicitly send it. Some more config. Not sure how much of this is needed.
(setq message-send-mail-function 'smtpmail-send-it
      smtpmail-starttls-credentials '(("localhost" 25 "[email protected]" nil))
      smtpmail-auth-credentials '(("localhost" 25 "[email protected]" nil))
      smtpmail-default-smtp-server "localhost"
      smtpmail-smtp-server "localhost"
      smtpmail-smtp-service 25
      smtpmail-local-domain "local.sachachua.com")
(setq send-mail-function 'smtpmail-send-it)
(setq smtpmail-smtp-server "127.0.0.1")
(setq smtpmail-smtp-service 25)
(setq user-mail-address "[email protected]")
Hide HTML mail. I need to fiddle with this some more, since Gnus still tries to display them. Sometimes my Gnus crashes when it tries to display HTML mail.
(setq mm-discouraged-alternatives
      '("text/html" "text/richtext")
      mm-automatic-display
      (-difference mm-automatic-display '("text/html" "text/enriched" "text/richtext")))
Hide quoted text.
(setq gnus-treat-hide-citation t)
Get smarter about filtering depending on what I reed or mark. I use ! (tick) for marking threads as something that interests me.
(setq gnus-use-adaptive-scoring t)
(setq gnus-default-adaptive-score-alist
     '((gnus-unread-mark)
       (gnus-ticked-mark (subject 10))
       (gnus-killed-mark (subject -5))
       (gnus-catchup-mark (subject -1))))

Cobbling together a semi-auto-responder using Emacs, Gnus, and org-contacts

It turns out that lots of people are interested in an e-mail-based course for learning Emacs Lisp. Yay! =) Maybe it's the idea of bite-size chunks. Maybe it's the ease of asking questions. Maybe it's the regular reminders to work on something. Who knows? Whatever the reason, it's awesome to see so many people willing to join me on this experiment.

Since this is my first time to venture into the world of teaching people online, I wanted to see how far I could push actually doing all the mails myself, instead of just signing up for an Aweber account and handing everyone off to an impersonal autoresponder. I dusted off Gnus, offlineimap, and org-contacts, and started figuring out my workflow. I'll share how that workflow's evolving so that you can get a sense of how someone might write little bits of Emacs Lisp to make something repetitive easier.

For the first little while, I got by with using C-x r s (copy-to-register) and C-x r i (insert-register) to store the text that I needed. Sometimes I needed to paste in the welcome message and checklist, and sometimes I needed to paste in the first lesson. By using registers, I could insert whatever I wanted instead of going through the kill ring. I also had another bit of templated code in yet another register so that I could easily create an org-contacts entry for the person whose mail I was replying to. In the beginning, I used tasks under each person's heading to indicate that I had sent them the checklist or that I had sent them the first lesson. Eventually, I changed my org-contacts notes so that the TODO state of each person showed which lesson I was going to send them next, or CHECKLIST if I was waiting for their reply to the checklist. I also set up Org so that it would automatically log when the TODO state was changed.

#+TODO: TODO | DONE
#+TODO: CHECKLIST(c!) BEGINNER1(1!) BEGINNER2(2!) BEGINNER3(3!) BEGINNER4(4!) FULL(f!) | FINISHED(x!)
#+TODO: | CANCELLED

* Who
** CHECKLIST Jane Smith ...
** BEGINNER1 John Smith
   SCHEDULED: <2014-05-28 Wed>
   :PROPERTIES:
   :EMAIL: [email protected]
   :END:
(notes from the messages, etc.)

I wrote some code to make it easier to send someone a checklist and create a note for them in my org-contacts file. I bound it to C-c e c for convenience. (The bind-key function is defined by a package.)

(setq sacha/elisp-course-checklist-body "... really long text here...")
(defun sacha/elisp-course-checklist ()
  "Copy this message and put it at the end as a checklist item. 
Start a message with the checklist."
  (interactive)
  (gnus-summary-scroll-up 1)
  (with-current-buffer gnus-article-buffer
    (let ((message (buffer-substring-no-properties (point-min) (point-max)))
          (email (cadr (org-contacts-gnus-get-name-email))))
      (with-current-buffer "elisp-course.org"
        (save-excursion
          (goto-char (point-max))
          (save-excursion
            (insert "\n** " message)
            (org-set-property "EMAIL" email)
            (org-todo "CHECKLIST"))))))
  (gnus-summary-followup-with-original nil)
  (goto-char (point-max))
  (insert sacha/elisp-course-checklist-body))
(bind-key "C-c e c" 'sacha/elisp-course-checklist)

This made it easier for me to read the starred messages from my inbox and use C-c e c to get a head start on processing people's introductory messages. Yay! I used the register trick to help me reply to people who were ready for the first lesson. After the first few replies, I noticed that the attachment code was fine even if I put that in the register too, so I added it as well.

Things got more complicated when I started processing lesson 2. I didn't want to have to set up and remember lots of different registers, and I didn't want to manually update the TODO states either. So I started defining functions that I could call with keyboard shortcuts:

(defun sacha/elisp-course-1 ()
  (interactive)
  (let ((marker (org-contacts-gnus-article-from-get-marker)))
    (if marker
        (org-with-point-at marker
          (org-todo "BEGINNER2"))))
  ;; Find the person's contact record
  (gnus-summary-scroll-up 1)
  (gnus-summary-followup-with-original nil)
  (message-goto-subject)
  (message-delete-line)
  (insert (concat "Subject: " sacha/elisp-course-1-subject "\n"))
  (goto-char (point-max))
  (insert sacha/elisp-course-1-body))
(bind-key "C-c e 1" 'sacha/elisp-course-1)
(defun sacha/elisp-course-2 ()
  (interactive)
  (let ((marker (org-contacts-gnus-article-from-get-marker)))
    (if marker
        (org-with-point-at marker
          (org-todo "BEGINNER3"))))
  ;; Find the person's contact record
  (gnus-summary-scroll-up)
  (gnus-summary-followup-with-original nil)
  (goto-char (point-max))
  (insert sacha/elisp-course-2-body))
(bind-key "C-c e 2" 'sacha/elisp-course-2)

Really, though, it doesn't make sense to have a lot of duplicated code. So I wrote some code that would use the person's TODO keyword to look up the message to send them, and then move them to the next keyword. Now I don't need sacha/elisp-course-1 or sacha/elisp-course-2 any more.

(setq sacha/elisp-course-info
      `(("CHECKLIST" nil ,sacha/elisp-course-checklist-body)
        ("BEGINNER1" ,sacha/elisp-course-1-subject ,sacha/elisp-course-1-body)
        ("BEGINNER2" ,sacha/elisp-course-2-subject ,sacha/elisp-course-2-body)))

(defun sacha/elisp-course-process (subject body &optional state)
  "Process this course entry."
  (if (derived-mode-p 'org-mode)
      (progn
        ;; Move this node to the next state and compose a message
        (if state (org-todo state))
        (org-todo 'right)
        (message-mail (org-entry-get (point) "EMAIL") subject)
        (goto-char (point-max))
        (insert body))
    ;; Doing this from Gnus; find the person's info
    (let ((marker (org-contacts-gnus-article-from-get-marker)))
      (if marker (org-with-point-at marker
                   (if state (org-todo state))
                   (org-todo 'right)))
      ;; Compose a reply
      (gnus-summary-scroll-up 1)
      (gnus-summary-followup-with-original nil)
      (message-goto-subject)
      (message-delete-line)
      (insert (concat "Subject: " subject "\n"))
      (goto-char (point-max))
      (insert body))))

(defun sacha/elisp-course-guess-and-process (&optional state)
  (interactive (list (if current-prefix-arg (read-string "State: "))))
  (let ((current-state
         (or state (elt
                    (if (derived-mode-p 'org-mode)
                        (org-heading-components) 
                      (let ((marker (org-contacts-gnus-article-from-get-marker)))
                        (if marker (org-with-point-at marker (org-heading-components)))))
                    2))))
    (sacha/elisp-course-process
     (elt (assoc current-state sacha/elisp-course-info) 1)
     (elt (assoc current-state sacha/elisp-course-info) 2)
     state)))
(bind-key "C-c e e" 'sacha/elisp-course-guess-and-process)

Come to think of it, I should totally have it schedule the next update for the next Wednesday, too. ;) That's just (org-schedule "+wed"). Neat, huh? And I'm sure there are all sorts of ways the code can be simpler, but it works for me at the moment, so hooray!

I really like this approach. It lets me pull in standard information while also letting me customize the messages and how it fits into my task tracking. I can't get that with Gmail (even with canned responses), and I'm not sure any CRM is going to be quite as awesome as this. I can't wait to see how else we'll tweak this as we go through more conversations. I'd like to get better at:

  • having a consistent place where I can process all the messages and make sure nothing falls through the cracks; I currently star messages to make sure I process them, since the Gmail label folder in IMAP seems to be missing some messages
  • seeing all Gnus conversations related to an org-contacts entry
  • reaching out to people proactively with the next lesson, even if they haven't e-mailed me (or maybe I should wait for them?)

Anyway, that's an example of writing a little bit of Emacs Lisp in order to connect different packages. Gnus handles mail, Org handles notes, org-contacts links the two together, and with a little bit of custom code, I can make the combination fit what I want to do. I read the source code of org-contacts to find out how I could look up the appropriate note, and I looked at org-shiftright to find out how to move things to the next TODO state. If you know something that works roughly like what you want it to work, you can find out how it does things and then copy that.

As for the course itself: I've been sending people links to the HTML output, attached .txt files (with -*- mode: org -*-) so they can open it in Emacs if they want, and inline text so that they can skim it briefly in their e-mail client if they want to. I'm not perfectly happy with the plain-text formats, but it seems to be a reasonable compromise, and so far people have been able to deal with it. I've been improving pieces of it based on feedback on clarity, suggestions for good examples, and so on. I didn't take all the feedback; after thinking about some of the suggestions, I still preferred it my way. It's shaping up quite nicely, though!

If you're curious about the beginner's course on reading Emacs Lisp, e-mail me at [email protected] and we'll see how this works out. I'm certainly learning a lot. =)

Emacs Gnus: Filter Spam

(draft for an upcoming book called Wicked Cool Emacs)

Ah, spam, the bane of our Internet lives. There is no completely reliable way to automatically filter spam. Spam messages that slip through the filters and perfectly legitimate messages that get labelled spam are all part of the occupational hazards of using the Internet.

The fastest way to filter spam is to use an external spam-filtering program such as Spamassassin or Bogofilter, so your spam can be filtered in the background and you don't have to spend time in Emacs filtering it yourself. In an ideal world, this would be done on the mail server so that you don't even need to download unwanted messages. If your inbox isn't full of ads for medicine or stocks, your mail server is probably doing a decent job of filtering the mail for you.

Server-based mail filtering

As spam filtering isn't an exact science, you'll want to find out how you can check your spam folder for misclassified mail. If you download your mail through POP, find out if there's a webmail interface that will allow you to check if any real mail has slipped into the junk mail pile. If you're on IMAP, your mail server might automatically file spam messages in a different group. Here's how to add the spam group to your list of groups:

  1. Type M-x gnus to bring up the group buffer.
  2. Type ^ (gnus-group-enter-server-mode).
  3. Choose the nnimap: entry for your mail server and press RET (gnus-server-read-server).
  4. Find the spam or junk mail group if it exists.
  5. Type u (gnus-browse-unsubscribe-current-group) to toggle the subscription. Subscribed groups will appear in your M-x gnus screen if they contain at least one unread message.

Another alternative is to have all the mail (spam and non-spam) delivered to your inbox, and then let Gnus be in charge of filing it into your spam and non-spam groups. If other people manage your mail server, ask them if you can have your mail processed by the spam filter but still delivered to your inbox. If you're administering your own mail server, set up a spam filtering system such as SpamAssassin or BogoFilter, then read the documentation of your spam filtering system to find out how to process the mail.

Spam filtering systems typically add a header such as "X-Spam-Status" or "X-Bogosity" to messages in order to indicate which messages are spam or even how spammy they are. To check if your mail server tags your messages as spam, open one of your messages in Gnus and type C-u g (gnus-summary-show-article) to view the complete headers and message. If you find a spam-related header such as X-Spam-Status, you can use it to split your mail. Add the following to your ~/.gnus:

 (setq spam-use-regex-headers t) ;; (1)
 (setq spam-regex-headers-spam "^X-Spam-Status: Yes")   ;; (2)
 (require 'spam) ;; (3)
 (spam-initialize) ;; (4)

This configures spam.el to detect spam based on message headers(1). Set spam-regex-headers-spam to a regular expression matching the header your mail server uses to indicate spam.(2) This configuration should be done before the spam.el library is loaded(3) and initialized(4), because spam.el uses the spam-use-* variables to determine which parts of the spam library to load.

In order to take advantage of this, you'll also need to add a rule that splits spam messages into a different group. If you haven't set up mail splitting yet, read qthe instructions on setting up fancy mail splitting in "Project XXX: Organize mail into groups". Add (: spam-split) to either nnmail-split-fancy or nnimap-split-fancy, depending on your configuration. For example, your ~/.gnus may look like this:

(setq nnmail-split-fancy
'(
;; ... other split rules go here ...
(: spam-split)
;; ... other split rules go here ...
"mail.misc")) ; default mailbox
(draft for an upcoming book called Wicked Cool Emacs, more to come!)

Emacs Gnus: Organize Your Mail

People handle large volumes of mail in different ways. Keeping everything in one mailbox can quickly become unmanageable because messages you need to read get lost among messages you don't need to read.

You can move mail manually by selecting them in the summary buffer and typing B m (gnus-summary-move-article). Then type the name of the group to which you would like to move the message. The group will be created if it doesn't exist.

To move multiple messages, mark them with # (gnus-summary-mark-as-processable) and then type B m (gnus-summary-move-article). To unmark a message, type M-# (gnus-summary-unmark-as-processable). To unmark all messages, type M P U (gnus-summary-unmark-all-processable).

Automatically filing mail

Moving messages by hand is tedious and time-consuming. One way to deal with this is to set up rules that automatically file mail into different groups (or folders, as they're called in other mail clients). Gnus calls this "splitting" mail, and you can split mail on IMAP servers as well as mail downloaded from POP3 servers to your computer.

For example, if you're using Gnus to read mail from an IMAP server, you can split your messages by adding this to your ~/.gnus:

 (setq nnimap-split-inbox "INBOX") ;; (1)
 (setq nnimap-split-predicate "UNDELETED") ;; (2)
 (setq nnimap-split-rule
       '(
         ("INBOX.emacs" "^Subject:.*emacs")
         ("INBOX.work" "^To:.[email protected]")    
         ("INBOX.personal" "^To:.[email protected]")    
         ("INBOX.errors" "^From:.*\\(mailer.daemon\\|postmaster\\)")   
        )) 

If you use a different inbox, change the value of nnimap-split-inbox(1). Any messages in the inbox will be split according to nnimap-split-rule(2), which is a list where each element is a list containing the group's name and a regular expression matching the header of messages that should be filed in the group. In this example, Gnus will move mail with subjects containing the word "emacs" to INBOX.emacs, mail directed to [email protected] to the INBOX.work group, mail directed to [email protected] to the INBOX.personal group, and mail error messages to INBOX.errors. All other messages will be stored in INBOX.

If you're downloading your mail from a POP3 server and storing it in nnml, add this to your ~/.gnus instead:

 (setq nnmail-split-methods
      '(
        ("mail.emacs" "^Subject:.*emacs")
        ("mail.work" "^To:.[email protected]")    
        ("mail.personal" "^To:.[email protected]")    
        ("mail.errors" "^From:.*\\(mailer.daemon\\|postmaster\\)")   
       )) 

All other messages will be stored in mail.misc.

Start M-x gnus again, and your mail will be split into the different groups.

Where are my groups?

If you don't see your new groups in the group buffer displayed by M-x gnus, type A A (gnus-group-list-active) to see all the groups. Go to the group that you would like to add to the group buffer, then type u (gnus-group-unsubscribe-current-group) to toggle its subscription. In this example, INBOX.automated is not subscribed to, but INBOX is.

 U    13: INBOX.automated 
      76: INBOX 

When you type M-x gnus again, you'll see your subscribed groups if they have unread messages.

nnimap-split-rule and nnmail-split-methods allow you to filter interesting or uninteresting mail into different groups based on their headers. Gnus comes with an even more powerful mail splitting engine. In fact, Gnus comes with "fancy mail splitting."

Fancy mail splitting

With fancy mail splitting and some configuration, you can split mail based on a combination of criteria. You can even manually file a message and have Gnus automatically file incoming replies in the same group.

To configure an IMAP connection to use fancy mail splitting, add the following to your ~/.gnus:

 (setq nnimap-split-inbox "INBOX")
 (setq nnimap-split-predicate "UNDELETED")
 (setq nnmail-split-fancy ;; (1)
       '(|                                ;; (2)
         (: gnus-registry-split-fancy-with-parent) ;; (3)
         ;; splitting rules go here       ;; (4)
         "INBOX"                          ;; (5)
        ))
 (setq nnimap-split-rule 'nnmail-split-fancy)
 (setq nnmail-split-methods 'nnimap-split-fancy) ;; (6)
 (gnus-registry-initialize) ;; (7)

This configures IMAP to use the nnmail-split-fancy function to determine the group for messages. Note that we're setting the nnmail-split-fancy variable here. If you want to process your IMAP mail separately from your other mail, you can set the nnimap-split-fancy variable instead. If so, also set nnimap-split-rule to 'nnimap-split-fancy. Using nnmail-split-fancy here makes the other examples easier to understand, though.

The nnmail-split-fancy variable controls the splitting behavior(1). The "|" symbol means that that the first matching rule is used(2). For example, if the message being processed is a reply to a message that Gnus knows about, then the gnus-registry-split-fancy-with-parent function will return the name of the group, and nnmail-split-fancy will file the message there(3). You can add other splitting rules as well(4). If messages don't match any of these rules, the last rule specifies that the messages will be filed in INBOX(5). Set nnmail-split-methods to nnimap-split-fancy as well in order to work around some assumptions in other parts of the code(6). After that, initialize the Gnus registry(7), which is responsible for tracking moved and deleted messages. This allows you to automatically split replies into the same folders as the original messages.

To configure fancy mail splitting with an nnml backend (suggested configuration for POP3), add the following to your ~/.gnus instead:

 (gnus-registry-initialize)
 (setq nnmail-split-fancy                 
       '(|                                
         (: gnus-registry-split-fancy-with-parent)
         ;; splitting rules go here       
         "mail.misc"                          ;; (1)
        ))
 (setq nnmail-split-methods 'nnmail-split-fancy)    

This code is similar to the IMAP example, except that the default mailbox name for nnml is mail.misc(1).

Here's how the previous rules in nnmail-split-methods would be translated to nnmail-split-fancy rules for an IMAP configuration:

 (setq nnmail-split-fancy
      '(|
        (: gnus-registry-split-fancy-with-parent)
         ;; splitting rules go here       
        (from mail "INBOX.errors")   ;; (1)
        (any "[email protected]" "INBOX.work")   ;; (2)
        (any "[email protected]" "INBOX.personal") ;; 
        ("subject" "emacs" "INBOX.emacs") ;; (3)
        "INBOX"    ;; or "mail.misc" for nnml/POP3
       )) 

The from keyword matches against the "From", "Sender", and "Resent-From" fields, while the mail keyword matches common mail system addresses(1). The corresponding to keyword matches against the "To", "Cc", "Apparently-To", "Resent-To" and "Resent-Cc" headers, while any matches the fields checked by the from and to keywords(2). You can also compare against the subject and other headers(3).

You can use logic in splitting rules, too. For example, if you like reading the jokes on [email protected], but you don't like the ones sent by [email protected] (he not only has a bad sense of humor, but also likes picking on Emacs!), you can use a rule like this in your nnmail-split-fancy:

         ;; ... other splitting rules go here...
         (any "[email protected]"   ;; (1)
              (| (from "[email protected]" "INBOX.junk") ;; (2)
                 "INBOX.jokes")) ;; (3)
         ;; ... other splitting rules go here

The first rule matches all messages with "[email protected]" in from- or to-related headers. Matching messages are processed with another split rule, which moves messages from [email protected] to a separate group(2) and files the other messages in INBOX.jokes(3). To learn more about creating complex rules, read the Gnus Info manual for "Fancy Mail Splitting".

Emacs Gnus: Searching Mail

There are several ways to find messages in Emacs. From the summary buffer, you can use / o (gnus-summary-insert-old-articles) to display all or some old messages. You can then scan through the headers in the summary buffer by using C-s (isearch-forward), or you can limit the displayed messages with these commands:

Messages from a given author/ agnus-summary-limit-to-author
Messages whose subjects matching a given regular expression/ /gnus-summary-limit-to-subject
Messages that match a given extra header/ xgnus-summary-limit-to-extra-headers
Messages at least N days old/ tgnus-summary-limit-to-age

Limits work on the messages that are currently displayed, so you can apply multiple limits. If you make a mistake, use / w (gnus-summary-pop-limit) to remove the previous limit. You can repeat / w (gnus-summary-pop-limit) until satisfied. To remove all the limits, type C-u / w (gnus-summary-popl-limit).

If you specify a prefix, the limit's meaning is reversed. For example, C-u / a (gnus-summary-limit-to-author) will remove the messages from the matching author or authors.

You can use Gnus to search the currently-displayed messages by using M-s (gnus-summary-search-article-forward) and M-r (gnus-summary-search-article-backward).

If you want to search a lot of mail, you'll find NNIR handy. NNIR is a front-end to mail search engines which can index your mail and return search results quickly. If you want to use NNIR with a local or remote IMAP server, you will need to use nnir.el and imap.el. If you download your mail using fetchmail or connect to a POP3 server and use an nnml backend, you can use NNIR with a search engine such as swish-e to search your ~/Mail directory efficiently.

1.6.7.1 Setting up IMAP and NNIR

If you use IMAP, then your mail is stored on the mail server and you'll need to use the IMAP search interface to search through it. Download nnir.el from http://www.emacswiki.org/cgi-bin/wiki/download/nnir.el and save it to your ~/elisp directory. You will also need an imap.el that is newer than the one that comes with Emacs 22. Download imap.el from http://www.emacswiki.org/cgi-bin/wiki/download/imap.el and save it to your ~/elisp directory as well. Because Gnus comes with an older version of imap.el, you will need to make sure that the new version of imap.el is loaded. Add the following to your ~/.gnus:

(add-to-list 'load-path "~/elisp")
(load-file "~/elisp/imap.el")
(require 'nnir)

Restart your Emacs. You can check if the correct version of imap.el has been loaded by typing M-x locate-library and specifying imap.el. If Emacs reports "~/elisp/imap.el", then Gnus is configured to use the updated imap.el.

1.6.7.2 Setting up POP3 and NNIR

If you use the configuration for POP3 that is suggested in this chapter, then your mail is stored in the nnml backend, which uses one file per message. To search this using NNIR, to install nnir.el and an external search mail engine. The Namazu search engine runs on Linux, UNIX, and Microsoft Windows, so that's what we'll talk about here. To find and configure other mail search engines supported by NNIR, check out the comments in nnir.el.

First, you'll need to download and install Namazu. If Namazu is available as a package for your distribution, install it that way, as it depends on a number of other programs. An installer for Microsoft Windows can be found at http://www.namazu.org/windows/ . If you need to build Namazu from source, you can get the source code and instructions from http://www.namazu.org .

After you've installed Namazu, create a directory for Namazu's index files, such as ~/.namazu-mail. Then index your mail by typing this at the command-line:

mknmz --mailnews -O ~/.namazu-mail ~/Mail

and add the following to your ~/.gnus:

(add-to-list 'load-path "~/elisp")
(require 'nnir)
(setq nnir-search-engine 'namazu)
(setq nnir-namazu-index-directory (expand-file-name "~/.namazu-mail"))
(setq nnir-namazu-remove-prefix (expand-file-name "~/Mail"))
(setq nnir-mail-backend gnus-select-method)
1.6.7.3 Searching your mail with NNIR

From the group buffer displayed by M-x gnus, you can type G G (gnus-group-make-nnir-group) to search your mail for a keyword.

If you're using the Namazu search engine, then you can use more sophisticated search queries such as:

Linux Emacsmessages that contain both "Linux" and "Emacs"
Linux or Emacsmessages that contain either "Linux" or "Emacs"
Emacs not Linuxmessages that contain "Emacs" but not "Linux"
Emacs and (Linux or Windows)messages that contain "Emacs" and either "Linux" or "Windows"
"apple pie"messages that contain the phrase "apple pie"
{apple pie}messages that contain the phrase "apple pie"
+from:[email protected]messages with [email protected] in the From: header
+subject:"apple pie"messages with the phrase "apple pie" in the Subject: header
+subject:apple +subject:piemessages whose Subject: headers contain both "apple" and "pie"

If matching messages are found, then you will see a temporary group with the results. Although you can't delete messages from this view, reading and replying to these messages is the same as reading and replying to regular messages.

To see a message in its original context, type G T (gnus-summary-nnir-goto-thread) from the summary buffer. This opens the message's original group. If Gnus asks you how many articles to load, press RET to accept the default of all the articles.


This is a draft for the Wicked Cool Emacs book I'm working on. =) Hope it helps!