The Complete Computing Environment

Gnus

LifeTechEmacsTopicsArcology

From the Gnus website:

Gnus is a flexible message reader running under GNU Emacs. It supports reading and composing both news and mail. In addition, it is able to use a number of web-based sources as inputs for its groups.

The main Gnus goal is to provide the user with an efficient and extensible interface towards dealing with large numbers of messages, no matter the form they may have or wherever they may come from.

Gnus is a fully MIME-compliant and supports reading and composing messages using any charset that GNU Emacs supports.

The same, but said differently: nearly any communication medium which can be represented as a collection of trees of messages could be plugged in to a single highly-customisable Emacs-based interface, and this interface can be customized like any other Emacs application.

Another quote from the Gnus documentation:

You know that Gnus gives you all the opportunity you'd ever want for shooting yourself in the foot. Some people call it flexibility. Gnus is also customizable to a great extent, which means that the user has a say on how Gnus behaves. Other newsreaders might unconditionally shoot you in your foot, but with Gnus, you have a choice!

Malleable Systems like Emacs provide the technically engaged end-user with a nearly endless arsenal of tools, and infinite ways to combine those tools. Emacs is not a tool so much as an workshop of ideas, a set of tools to design the ideal workshop.

Below I describe a core configuration for the Gnus message reader as a part of The Complete Computing Environment and my Email and News and Information Pipelines. It includes, as well, a set of Evil Mode bindings which will allow me to use Gnus with the same idioms as the rest of my system. I aim to describe source-specific configuration in other documents, this module will be "pluggable" in that regard, designed to integrate in this CCE system. I also configure and describe a unique feature of Gnus, the Gnus Scoring System and the Adaptive Scoring engine but individual scoring decisions are left as an excercise to the reader.

(provide 'cce/gnus)

You know who I am, you know my primary email alias:

(setq user-mail-address "ryan@whatthefuck.computer"
      user-full-name    "Ryan Rix")

A Somewhat Aesthetically Pleasing Summary Buffer

Despite all assumptions, I want to work with things which are aesthetically appealing. Gnus can use unicode line-drawing symbols to present threads in a more-legible fashion, and I configure that below, based on configuration taken from the Spacemacs Gnus Layer. Additionally, I configure the gnus-summary-line-format to be slightly more compact than the default configuration.

(setq gnus-summary-line-format "%*%U%R%z %(%&user-date; %-15,15f  %B%s%)\n"
      gnus-summary-thread-gathering-function 'gnus-gather-threads-by-references
      gnus-sum-thread-tree-false-root ""
      gnus-sum-thread-tree-indent " "
      gnus-sum-thread-tree-leaf-with-other "├► "
      gnus-sum-thread-tree-root ""
      gnus-sum-thread-tree-single-leaf "╰► "
      gnus-sum-thread-tree-vertical "│"
      gnus-user-date-format-alist '((t . "%d %b %Y %H:%M")))

Custom Window Configurations

Gnus has a reasonable UI by default and it is of course wildly customisable, every Gnus buffer can be configured to be displayed in a different configuration, this is described in gnus-buffer-configuration. I modify the windowing configuration slightly: I do not need to see the group buffer (the buffer which has the full list of groups) when I am looking at articles, so I override it:

(require 'gnus-win)
(add-to-list 'gnus-buffer-configuration
             '(article 
               (horizontal 1.0
                           ;; (org-todo 0.3)
                           (vertical 1.0
                                     (summary 0.25 point)
                                     (article 1.0)))))

(add-to-list 'gnus-buffer-configuration
             '(group 
               (horizontal 1.0
                           ;; (org-todo 0.3)
                           (group 1.0 point))))

(add-to-list 'gnus-buffer-configuration
             '(summary 
               (horizontal 1.0
                           ;; (org-todo 0.3)
                           (summary 1.0 point))))

(add-to-list 'gnus-window-to-buffer '(org-todo . "emails.org"))
(with-eval-after-load 'org-agenda
  (add-to-list 'org-agenda-files "~/org/emails.org"))

Use fill-column to wrap HTML text in Gnus buffers:

(add-hook 'gnus-article-mode-hook
          (lambda ()
            (cond
             ((cce/external-display-connected)
              (setq fill-column 120
                    shr-use-fonts nil))
             ((equal (system-name) "MeadowCrush")
              (setq fill-column 75
                    shr-use-fonts t))
             ((equal (system-name) "tres-ebow")
              (setq fill-column 90
                    shr-use-fonts t))
             (t
              (setq fill-column 120
                    shr-use-fonts t)))))

Rich Text, HTML mail, and Images

I want to not look at HTML unless I have to, and I want it to not load images unless they're embedded

I do not want my client to display HTML or rich-text by default and to prefer plain text. This is not simply an ideological choice: Gnus has access to a limited selection of HTML renderers, configurable in mm-text-html-renderer, the default and my preferred is to use the shr engine which is included by default in Emacs and is also used by EWW. This engine supports a simple limited HTML subset, supports images, and doesn't require any software to be installed.

I certainly don't want to view the HTML rendering by default, though, especially if you're sending me crapware/advertisement/newsletter mail. shr can be configured to not load remote images, preventing tracking pixels or other remote content from loading, but it's often a much less legible interface than a simple text encoding, even if buttons or links rarely work by default. As of Gnus 5.13 ([2020-04-30]), when gnus-inhibit-images is set to true Gnus will skip loading all images, but this is not actually what I want: my Universal Aggregator tool will re-write remote images from RSS feeds to use cid: URLs and embed the images in the MIME container. So, following the Gnus HTML documentation and the logic in gnus-html-wash-images from gnus-html.el, I am setting gnus-blocked-images to a constantly-matching regular expression ".". This is the default behavior but I encode it here explicitly because it is an explicit decision.

(with-eval-after-load 'mml
  (require 'mm-decode)
  (setq mm-discouraged-alternatives '("text/html" "text/richtext")
        mm-automatic-display (remove "text/html" mm-automatic-display))
  (setq mm-text-html-renderer 'shr
        gnus-blocked-images "."
        gnus-inhibit-images nil))

Configuring Usenet as the Default Access Method

I access many mailing lists through the Gmane service, a proxy service which presents many user mailing lists as usenet groups and I can subscribe to them only in this client, not busying other mailboxes with a bunch of mailing lists, keeping filters up to date, etc.

(setq gnus-select-method '(nntp "news.gmane.io"))

Local Maildir access with the Dovecot IMAP command

I use mbsync to move mail from my server to Maildirs on my laptop, desktop, and GPD Pocket. Gnus provides a native backend for reading maildirs, but it performs quite poorly and the universal suggestion is to use an IMAP daemon configured in some fashion to access that directory using the much quicker nnimap connector.

Rather than deal with the full authentication stack and having an IMAP server running locally on all my laptops, Gnus can use a shell-program which takes IMAP commands on stdin and outputs results on stdout. Dovecot ships one of these in its libexec folder, and with a tweaked mail_location setting it can use my mbsync folders without issue.

mail_location = maildir:~/Maildir/:LAYOUT=fs:INBOX=~/Maildir/fastmail/INBOX
protocols = imap

protocol imap {
  listen = 127.0.0.1:143
}

auth_mechanisms = anonymous

That configuration file is stashed in my system-path with home-manager in a wrapper called dovecot-local-imap:

{pkgs, config, ...}:

let
  dovecotLocalConf = ../files/dovecot-noauth.conf;
  dovecotLocalImap = pkgs.writeScriptBin "dovecot-local-imap" ''
    #!${pkgs.stdenv.shell}

    ${pkgs.dovecot}/libexec/dovecot/imap -c ${dovecotLocalConf}
  '';
in {
  home.packages = [ dovecotLocalImap ];

  home.activation = {
    gnus-newsrc =
      pkgs.lib.mkActivationLocalLink (builtins.trace config)
        "~/sync/private-files/.newsrc"
        ".newsrc";
    gnus-newsrc_eld =
      pkgs.lib.mkActivationLocalLink config
        "~/sync/private-files/.newsrc.eld"
        ".newsrc.eld";
    gnus-feeds =
      pkgs.lib.mkActivationLocalLink config
        "~/Maildir/endpoint/feeds"
        "Maildir/feeds";
    gnus-fastmail =
      pkgs.lib.mkActivationLocalLink config
        "~/Maildir/endpoint/fastmail"
        "Maildir/fastmail";
  };
}

Finally, this ugly function and sharp-quoted setq tries to find an IMAP which works. On my non-Nix systems it could be in any number of places depending on distro packages..

(defun cce/find-dovecot ()
  (cond ((executable-find "dovecot-local-imap") (executable-find "dovecot-local-imap"))
        ((file-exists-p "/usr/lib/dovecot/imap") "/usr/lib/dovecot/imap")
        ((file-exists-p "/usr/libexec/dovecot/imap") "/usr/libexec/dovecot/imap")))

(setq gnus-secondary-select-methods
      `((nnimap "maildir"
                (nnimap-stream shell)
                (nnimap-inbox ,(cond ((equal "work" (system-name))
                                      "gmail/Inbox")
                                     (t "fastmail/INBOX")))
                (nnir-search-engine imap)
                (nnimap-shell-program ,(cce/find-dovecot)))))

Put All Together With Noweb

All of the code presented above is inserted in to a use-package block1.

(use-package gnus
  :ensure nil
  :config
  <<image-conf>>
  <<no-group-in-article>>
  <<shr-wrap>>
  <<summary-appearance>>
  <<gmane>>
)

This is considered a "minimum viable" gnus configuration for my purposes, the sort of core-behavior of the Gnus system. There is more configuration for my configuration stack linked from Email and News and Information Pipelines, some of which is specific to Gnus:


  1. Configure Packaging↩︎