Mail

Emanix ships no mail client. What it ships is the seam a mail client plugs into: an Emacs that is already the desktop, a secret store for the credentials, and the consumer's own bin directory for helper scripts.

So this page documents a shape rather than a distribution feature. The account, the provider, the folders and the key binding all belong to the consuming flake. The shape described here is the one this deployment runs — a local Maildir filled by mbsync, indexed by mu, read in Emacs through mu4e.

1. Read mail

C-c M opens mu4e. The distribution binds nothing for mail; that binding is the consumer's, as is the package.

Key Does
RET Open the message
j Jump to a maildir
C-c C-u Refresh — sync, then reindex
U Mark for refiling (archive)
x Execute the marks
q Quit

Sync is manual on purpose: nothing polls a mail server in the background.

2. Three programs, and which one is broken

  • mbsync (from isync) copies IMAP into a local Maildir
  • mu indexes that Maildir into a database
  • mu4e queries the index and shows the result

mu4e is only the frontend. When mail looks stale or missing, the useful question is which of the first two stages stopped — and C-c C-u runs both, which is why it is the answer to most of this page.

The maildir root is set once, outside Emacs, and never in elisp:

mu init --maildir=~/.mail --my-address=<address>
mu index

Folder names in the configuration are relative to that root — /<account>/Sent and the like. There is deliberately no maildir setq in the elisp: in mu4e 1.14 mu4e-root-maildir is a function that asks mu where the maildir is, and plain mu4e-maildir is not a variable at all. Setting either creates a global that nothing reads, while looking exactly like configuration.

2.1. The daily surface is bookmarks

The default view is a list of saved queries rather than one inbox: unread everything, today, this week, and the inbox proper. They are plain mu4e bookmarks, so editing them is editing a list in the consumer's config — the distribution has no opinion about what a mail view should be.

3. Credentials come from the secret store

Both halves read agenix secrets at run time instead of files in the home directory:

  • IMAP — mbsync's PassCmd reads /run/agenix/mbsync-pass
  • SMTP — auth-sources is set to /run/agenix/authinfo

The second is worth spelling out. auth-sources' default is ~/.authinfo, which does not exist here, and the failure is silent: Emacs prompts for a password instead of reporting a missing file. Naming the decrypted path is what makes that failure loud. See Secrets.

Two consequences follow from the store being regenerated at activation. Editing anything under /run/agenix does nothing. And adding an account means editing the plaintext, re-encrypting, and rebuilding.

PassCmd, never Pass. The sync configuration is store-managed and the store is world-readable, so a literal password in it would be readable by every user on the machine. Reading the secret at run time is what makes the file safe to keep in the flake at all.

4. Two traps

  • Port 465 is implicit TLS. smtpmail-stream-type must be 'tls, not 'starttls — 465 wraps the connection from the first byte. A comment in this deployment claimed STARTTLS for a while while the code was correct, which is the kind of discrepancy that costs an afternoon.
  • The sync channel needs Patterns *. Without it mbsync syncs the inbox only, while the frontend refers to Sent, Drafts, Archive and Trash. Everything looks fine until you go looking for a message you sent.

A third one is worth knowing if you pick bindings: C-c M is capital for a reason. C-c m is a different command map, and taking it would have quietly removed the commands living under it.

5. On a new machine

Install the stack, run mu init and mu index once, then C-c M. The index lives under ~/.cache/mu — runtime state, not in the flake, and not recreated by a rebuild.

In the consuming flake the stack is installed for graphical hosts only, while the elisp is guarded so the same config still loads where mu4e is absent:

(condition-case mu4e-err
    (when (require 'mu4e nil :no-error)
      ...)
  (error (message "mu4e block skipped (%S)" mu4e-err)))

That guard is not decoration. The block signalled once, on the first host that actually had mu4e installed, and because the whole personal config is wrapped in one condition-case, every form after it was silently discarded — taking unrelated settings down with it. A mail client that fails to load must not be able to break the editor.

6. Where each piece lives

  • The elisp, the sync config, the account and the binding — the consuming flake. The sync file is linked from the flake into ~/.mbsyncrc, because that is the path mbsync reads.
  • The mu4e package — the consumer's, built for the same emacs-pgtk the desktop runs. The distribution does not ship it.
  • The credentials — the distribution's secret store, described in Secrets.

None of it is distribution policy. A consumer who prefers another frontend, another sync tool or another provider keeps the rest of the seam — the store, the Emacs, the bin directory — unchanged.