Amin Bandali: Reading and writing emails in GNU Emacs with Gnus
At the 10th anniversary of my involvement in EmacsConf, I’m finally
giving my first ever talk at the conference, for EmacsConf 2025. 🙂
In this talk, I give a quick introduction to Gnus and show a basic
configuration for reading and writing email with Gnus and Message.
You can watch the video below, or from the talk’s page on the
EmacsConf 2025 wiki: https://emacsconf.org/2025/talks/gnus
The above video is provided with closed captions and a transcript
— thanks, Sacha!
A commented copy of the init file from the video is provided below.
Happy hacking!
;;; emacsconf-2025-gnus.el -*- lexical-binding: t -*-
;; This file is marked with CC0 1.0 Universal
;; and is dedicated to the public domain.
;; Note: this file uses the `setopt' macro introduced in Emacs 29
;; to customize the value of user options. If you are using older
;; Emacsen, you may can use `customize-set-variable' or `setq'.
;;; Init / convenience
;; Initialize the package system.
(require 'package)
(package-initialize)
(setopt
;; Explicitly set `package-archives', in part to ensure https ones
;; are used, and also to have NonGNU ELPA on older Emacsen as well.
package-archives
'(("gnu" . "https://elpa.gnu.org/packages/")
("nongnu" . "https://elpa.nongnu.org/nongnu/")))
;; Download descriptions of available packages from the above
;; package archives.
(unless package-archive-contents
(package-refresh-contents))
;; Install the keycast package if not already installed.
(dolist (package '(keycast))
(unless (package-installed-p package)
(package-install package)))
;; Enable keycast to show the current command and its binding in
;; the mode line, for the presentation.
(setopt keycast-mode-line-remove-tail-elements nil)
(when (fboundp #'keycast-mode-line-mode)
(keycast-mode-line-mode 1))
;; Set a font with larger size for the presentation.
;; It requires that the Source Code Pro be installed on your
;; system. Feel free to comment out or remove.
(when (display-graphic-p)
(with-eval-after-load 'faces
(let ((f "Source Code Pro Medium-15"))
(set-face-attribute 'default nil :font f)
(set-face-attribute 'fixed-pitch nil :font f))))
;; Inline function for expanding file and directory names inside
;; `user-emacs-directory'. For example: (+emacs.d "gnus/")
(defsubst +emacs.d (path)
"Expand PATH relative to `user-emacs-directory'."
(expand-file-name
(convert-standard-filename path) user-emacs-directory))
(keymap-global-set "C-c e e" #'eval-last-sexp)
;; Add the info directory from the GNU Emacs source repository to
;; the list of directories to search for Info documentation files.
;; Useful if you're using Emacs directly built from a source
;; repository, rather than installed on your system.
(with-eval-after-load 'info
(setq
Info-directory-list
`(,@Info-directory-list
,(expand-file-name
(convert-standard-filename "info/") source-directory)
"/usr/share/info/")))
␌
;;; Gnus configuration
;; (info "(gnus) Don't Panic")
(keymap-global-set "C-c g" #'gnus)
(setopt
user-full-name "Gnus Fan Emacsian"
user-mail-address "ec25gnus@kelar.org")
;; Tell Emacs we'd like to use Gnus and its Message integration
;; for reading and writing mail.
(setopt
mail-user-agent 'gnus-user-agent
read-mail-command #'gnus)
;; Consolidate various Gnus files inside a gnus directory in the
;; `user-emacs-directory'.
(setopt
gnus-home-directory (+emacs.d "gnus/")
gnus-directory (+emacs.d "gnus/news/")
message-directory (+emacs.d "gnus/mail/")
nndraft-directory (+emacs.d "gnus/drafts/"))
(setopt ; don't bother with .newsrc, use .newsrc.eld instead
gnus-save-newsrc-file nil
gnus-read-newsrc-file nil)
;; Don't prompt for confirmation when exiting Gnus.
(setopt gnus-interactive-exit nil)
;; Configure two IMAP mail accounts.
(setopt
gnus-select-method '(nnnil "")
gnus-secondary-select-methods
'((nnimap
"ec25gnus"
(nnimap-stream tls)
(nnimap-address "mail.kelar.org")
;; (nnimap-server-port 993) ; imaps
(nnimap-authenticator plain)
(nnimap-user "ec25gnus@kelar.org"))
(nnimap
"ec25work"
(nnimap-stream tls)
(nnimap-address "mail.kelar.org")
;; (nnimap-server-port 993) ; imaps
(nnimap-authenticator plain)
(nnimap-user "ec25work@kelar.org")
;; Archive messages into yearly Archive folders upon pressing
;; 'E' (for Expire) in the summary buffer.
(nnmail-expiry-wait immediate)
(nnmail-expiry-target nnmail-fancy-expiry-target)
(nnmail-fancy-expiry-targets
(("from" ".*" "nnimap+ec25work:Archive.%Y"))))))
;; `init-file-debug' corresponds to launching emacs with --debug-init
(setq nnimap-record-commands init-file-debug)
;; The "Sent" folder
(setopt gnus-message-archive-group "nnimap+ec25gnus:INBOX")
;;;; Group buffer
;; Always show INBOX groups even if they have no unread or ticked
;; messages.
(setopt gnus-permanently-visible-groups ":INBOX$")
;; Enable topic mode in the group buffer, for classifying groups.
(add-hook 'gnus-group-mode-hook #'gnus-topic-mode)
;;;; Article buffer
;; Display the following message headers in Article buffers,
;; in the given order.
(setopt
gnus-sorted-header-list
'("^From:"
"^X-RT-Originator"
"^Newsgroups:"
"^Subject:"
"^Date:"
"^Envelope-To:"
"^Followup-To:"
"^Reply-To:"
"^Organization:"
"^Summary:"
"^Abstract:"
"^Keywords:"
"^To:"
"^[BGF]?Cc:"
"^Posted-To:"
"^Mail-Copies-To:"
"^Mail-Followup-To:"
"^Apparently-To:"
"^Resent-From:"
"^User-Agent:"
"^X-detected-operating-system:"
"^X-Spam_action:"
"^X-Spam_bar:"
"^Message-ID:"
;; "^References:"
"^List-Id:"
"^Gnus-Warning:"))
;;;; Summary buffer
;; Fine-tune sorting of threads in the summary buffer.
;; See: (info "(gnus) Sorting the Summary Buffer")
(setopt
gnus-thread-sort-functions
'(gnus-thread-sort-by-number
gnus-thread-sort-by-subject
gnus-thread-sort-by-date))
;;;; Message and sending mail
(setopt
;; Automatically mark Gcc (sent) messages as read.
gnus-gcc-mark-as-read t
;; Configure posting styles for per-account Gcc groups, and SMTP
;; server for sending mail. See: (info "(gnus) Posting Styles")
;; Also see sample .authinfo file provided below.
gnus-posting-styles
'(("nnimap\\+ec25gnus:.*"
(address "ec25gnus@kelar.org")
("X-Message-SMTP-Method" "smtp mail.kelar.org 587")
(gcc "nnimap+ec25gnus:INBOX"))
("nnimap\\+ec25work:.*"
(address "ec25work@kelar.org")
("X-Message-SMTP-Method" "smtp dasht.kelar.org 587")
(gcc "nnimap+ec25work:INBOX"))))
(setopt
;; Ask for confirmation when sending a message.
message-confirm-send t
;; Wrap messages at 70 characters when pressing M-q or when
;; auto-fill-mode is enabled.
message-fill-column 70
;; Forward messages (C-c C-f) as a proper MIME part.
message-forward-as-mime t
;; Send mail using Emacs's built-in smtpmail library.
message-send-mail-function #'smtpmail-send-it
;; Omit our own email address(es) when composing replies.
message-dont-reply-to-names "ec25\\(gnus\\|work\\)@kelar\\.org"
gnus-ignored-from-addresses message-dont-reply-to-names)
;; Unbind C-c C-s for sending mail; too easy to accidentally hit
;; instead of C-c C-d (save draft for later)
(keymap-set message-mode-map "C-c C-s" nil)
;; Display a `fill-column' indicator in Message mode.
(add-hook 'message-mode-hook #'display-fill-column-indicator-mode)
;; Enable Flyspell for on-the-fly spell checking.
(add-hook 'message-mode-hook #'flyspell-mode)
Sample ~/.authinfo file:
machine ec25gnus login ec25gnus@kelar.org password hunter2
machine ec25work login ec25work@kelar.org password badpass123
machine mail.kelar.org login ec25gnus@kelar.org password hunter2
machine dasht.kelar.org login ec25work@kelar.org password badpass123
Note that for purpose of storing credentials for use by Gnus’s select
methods, the machine portions need to match the names we give our
select methods when configuring gnus-secondary-select-methods,
namely ec25gnus and ec25work in our example.
We also store a copy of the credentials for use by Emacs’s smtpmail
when sending mail, where the machine must be the fully-qualified
domain name (FQDN) of the SMTP server we specify with the
X-Message-SMTP-Method header for each account by defining a
corresponding rule for it in gnus-posting-styles.
Lastly, I recommend using an encrypted authinfo file by saving it as
~/.authinfo.gpg instead to avoid storing your credentials in plain
text. If you set up Emacs’s EasyPG, it will seamlessly decrypt or
encrypt the file using GPG when reading from or writing to it.
Type C-h v auth-sources RET to see the documentation of the
auth-sources variable for more details.
Source: Planet GNU