Andrew Mercer
on this page

Mutt is a small, fast, entirely keyboard-driven mail client for the terminal. It does one job, reading and composing mail, and leaves everything else to other tools: your editor writes the message, GnuPG signs it, an MTA or SMTP server delivers it, and optionally a sync tool fetches it. That separation is why a muttrc written a decade ago usually still works. It is also why Mutt's configuration can seem large at first.

This guide builds a configuration from the ground up. GPG gets its own guide (Mutt and GPG). The NeoMutt fork reads almost all of this config unchanged; see NeoMutt for what differs.

Install and check the build

sudo dnf install mutt        # Fedora / RHEL
sudo apt install mutt        # Debian / Ubuntu
brew install mutt            # macOS

Then confirm what your build supports. IMAP, SMTP, SASL, header caching and GPGME are compile-time options:

mutt -v | grep -oE '[+-](USE_IMAP|USE_SMTP|USE_SASL|USE_SSL_OPENSSL|USE_SSL_GNUTLS|HCACHE|CRYPT_BACKEND_GPGME)'

A + means it's compiled in. Distribution packages normally enable all of these.

SMTP AUTH relies on Cyrus SASL plugins that some distributions package separately. If sending fails with No authenticators available, install them:

sudo apt install libsasl2-modules          # Debian / Ubuntu
sudo dnf install cyrus-sasl-plain          # Fedora / RHEL

Where the configuration lives

Mutt reads the first of these it finds: ~/.muttrc, ~/.mutt/muttrc, or $XDG_CONFIG_HOME/mutt/muttrc (usually ~/.config/mutt/muttrc). A layout that scales well past one account:

~/.config/mutt/
├── muttrc                 # common settings: UI, editor, sorting, bindings
├── accounts/
│   ├── personal.muttrc    # per-account: servers, folders, identity
│   └── work.muttrc
├── signature
├── aliases                # address book (written by Mutt's `a` key)
├── gpg.muttrc             # crypto settings (see Mutt and GPG)
└── cache/
    ├── personal/{headers,bodies}
    └── work/{headers,bodies}
mkdir -p ~/.config/mutt/{accounts,cache/{personal,work}/bodies}
touch ~/.config/mutt/{aliases,signature}
chmod 700 ~/.config/mutt

A minimal single-account configuration

Start here. Everything later in the guide is refinement.

# ~/.config/mutt/muttrc

# ---- Identity ---------------------------------------------------------------
set realname   = "Your Name"
set from       = "[email protected]"
set use_from   = yes                  # generate a From: header from $from
set use_envelope_from = yes           # also use it as the SMTP envelope sender

# ---- Reading (IMAP) ---------------------------------------------------------
set imap_user  = "[email protected]"
set folder     = "imaps://imap.domain.tld:993/"
set spoolfile  = "+INBOX"              # + (or =) means "relative to $folder"
set postponed  = "+Drafts"
set record     = "+Sent"
set trash      = "+Trash"
set imap_check_subscribed = yes       # add server-subscribed folders to the mailbox list
set imap_keepalive = 300
set mail_check     = 60               # seconds between new-mail checks
set timeout        = 15               # how often to check while idle at a prompt

# ---- Sending (SMTP submission) ----------------------------------------------
set smtp_url   = "smtp://[email protected]@smtp.domain.tld:587/"
set ssl_starttls  = yes               # upgrade plain connections with STARTTLS
set ssl_force_tls = yes               # refuse to send credentials unencrypted

# ---- Caching ----------------------------------------------------------------
set header_cache     = "~/.config/mutt/cache/personal/headers"
set message_cachedir = "~/.config/mutt/cache/personal/bodies"
set certificate_file = "~/.config/mutt/certificates"

With no imap_pass/smtp_pass set, Mutt prompts for passwords on connect, which is the simplest secure default. See Keeping passwords out of the muttrc below for non-interactive options.

What the key variables mean

Variable Purpose
folder The base for relative mailbox names (+Sent, =Drafts)
spoolfile The mailbox Mutt opens at startup ("inbox"), reachable with !
record Where copies of sent mail go (< shortcut). Set it to "" if the server files sent mail itself
postponed Drafts saved with postpone (R recalls them)
mbox Where read mail is moved on exit if move=yes (> shortcut). Usually leave move=no with IMAP
smtp_url smtp:// for STARTTLS on 587, smtps:// for implicit TLS on 465. If unset, Mutt pipes to $sendmail instead
ssl_force_tls Never talk to a server unencrypted. Always set this

IMAP vs SMTP: two separate connections

Reading and sending are completely independent in Mutt, as they are in the protocol (see Email Architecture). A working inbox says nothing about whether sending works, and the reverse is also true. Debug each separately.

You also have three choices for sending:

  1. smtp_url: Mutt speaks SMTP submission directly to a provider. This is the most common choice.
  2. $sendmail (the default when smtp_url is unset): Mutt pipes the message to /usr/sbin/sendmail -oem -oi. Use this when the box runs a local Postfix null client or msmtp-mta that already holds the relay credentials (see Relaying Outbound Mail). Credentials then live in one place for every tool on the host.
  3. msmtp explicitly: set sendmail = "/usr/bin/msmtp -a work". This is handy when msmtp is already configured with per-account profiles.

Gmail

Gmail works well with Mutt, with a few specifics:

set imap_user  = "[email protected]"
set folder     = "imaps://imap.gmail.com:993/"
set spoolfile  = "+INBOX"
set postponed  = "+[Gmail]/Drafts"
set trash      = "+[Gmail]/Trash"
set record     = ""                    # Gmail's SMTP server files sent mail itself
set mbox       = "+[Gmail]/All Mail"
set smtp_url   = "smtps://[email protected]@smtp.gmail.com:465/"
set ssl_force_tls = yes
  • record = "": anything sent through smtp.gmail.com is automatically saved to Sent Mail. Setting record as well creates duplicates.
  • Deleting vs archiving: in Gmail, "delete" from INBOX just removes the Inbox label. The message stays in All Mail. To actually bin a message, move it to [Gmail]/Trash (with trash set, d then $ does this).
  • Authentication: Google no longer accepts an account's normal password from third-party clients. The old "Allow less secure apps" toggle is gone. You have two options:
  • App password: requires 2-Step Verification on the account. Create one under Google Account → Security → App passwords and use it as both imap_pass and smtp_pass. This is simple and works with any Mutt build. Google Workspace admins can disable app passwords.
  • OAuth2: Mutt 2.0+ supports XOAUTH2/OAUTHBEARER, and ships a helper script, mutt_oauth2.py, in its contrib directory. You register an OAuth client in a Google Cloud project, run the script once to authorize, and it stores an encrypted refresh token (GPG-encrypted by default). The script's README walks through the Google side.

    muttrc set imap_authenticators = "oauthbearer:xoauth2" set imap_oauth_refresh_command = "~/.config/mutt/mutt_oauth2.py ~/.config/mutt/[email protected]" set smtp_authenticators = $imap_authenticators set smtp_oauth_refresh_command = $imap_oauth_refresh_command

  • IMAP access: current Gmail accounts have IMAP enabled permanently. If you are on an older account and connections are refused, check Settings → Forwarding and POP/IMAP.

Keeping passwords out of the muttrc

Never put a plaintext password in a muttrc. Config files get copied into dotfile repos, backups and pastebins. If a password has ever been committed or published in one, rotate it.

From best to most basic:

1. A password manager via backticks. Backticks are evaluated once, at startup:

set my_pass = "`pass show email/[email protected] | head -n1`"
set imap_pass = $my_pass
set smtp_pass = $my_pass

my_* variables are user-defined and expand like built-ins. Any CLI password manager works: pass, gopass, secret-tool lookup …, op read …, rbw get ….

2. A GPG-encrypted settings file. Mutt can source the output of a command if the filename ends with |:

source "gpg --quiet --decrypt ~/.config/mutt/secrets.muttrc.gpg |"
# Create it once:
cat > /tmp/secrets.muttrc <<'EOF'
set imap_pass = "..."
set smtp_pass = "..."
EOF
gpg --encrypt --recipient [email protected] -o ~/.config/mutt/secrets.muttrc.gpg /tmp/secrets.muttrc
shred -u /tmp/secrets.muttrc

3. Prompt. Leave imap_pass/smtp_pass unset and Mutt asks once per session.

Multiple accounts

Mutt has no built-in notion of an "account". There are two well-trodden approaches, and the simpler one is usually better.

Put everything common in one file and everything account-specific in another. Then start a separate Mutt for each account. Nothing from one account can leak into another, because they never share a process.

# ~/.config/mutt/muttrc  (common settings only, no servers or identities)
set editor = "vim"
set sort = threads
# ...
# ~/.config/mutt/accounts/work.muttrc
source ~/.config/mutt/muttrc
set realname = "Your Name"
set from     = "[email protected]"
set imap_user = "[email protected]"
set folder    = "imaps://imap.work.tld/"
set spoolfile = "+INBOX"
set record    = "+Sent Items"
set postponed = "+Drafts"
set smtp_url  = "smtp://[email protected]@smtp.work.tld:587/"
set header_cache     = "~/.config/mutt/cache/work/headers"
set message_cachedir = "~/.config/mutt/cache/work/bodies"
set my_pass = "`pass show email/[email protected] | head -n1`"
set imap_pass = $my_pass
set smtp_pass = $my_pass
# ~/.bashrc or ~/.zshrc
alias mutt-personal='mutt -F ~/.config/mutt/accounts/personal.muttrc'
alias mutt-work='mutt -F ~/.config/mutt/accounts/work.muttrc'

Run each in its own tmux window or screen session. A variant of the same idea uses one shared muttrc whose last line is source ~/.config/mutt/accounts/$MUTT_ACCOUNT.muttrc, with aliases setting MUTT_ACCOUNT=work before launching. Kevin McCarthy's long-standing Mutt tips popularized this per-process pattern.

Approach B: several accounts in one Mutt

Mutt 1.5.17+ has account-hook, which sets connection variables per server, and folder-hook, which changes identity per folder. Combined with macros, you can switch accounts inside one session:

# Credentials and connection settings per IMAP server
account-hook imaps://you@imap.domain.tld/ 'set [email protected] imap_pass=$my_pass_personal'
account-hook imaps://you@imap.work.tld/   'set [email protected]   imap_pass=$my_pass_work'

# Identity follows whichever folder you're in
folder-hook 'imaps://you@imap.domain.tld/' 'source ~/.config/mutt/accounts/personal.muttrc'
folder-hook 'imaps://you@imap.work.tld/'   'source ~/.config/mutt/accounts/work.muttrc'

# Start in personal; switch with F2 / F3
source ~/.config/mutt/accounts/personal.muttrc
macro index <f2> '<sync-mailbox><enter-command>source ~/.config/mutt/accounts/personal.muttrc<enter><change-folder>!<enter>'
macro index <f3> '<sync-mailbox><enter-command>source ~/.config/mutt/accounts/work.muttrc<enter><change-folder>!<enter>'

The pitfall: every per-account file must set every variable that differs between accounts (from, record, postponed, smtp_url, caches, signature, PGP key). Any variable left out keeps the previous account's value, and you send from the wrong address. Approach A avoids this by construction.

Reading: headers, sorting and threading

# Show only the headers you care about, in this order
ignore *
unignore from: to: cc: date: subject: reply-to: list-id:
hdr_order from: to: cc: date: subject:

# Threads, newest activity at the bottom (top with reverse-)
set sort     = threads
set sort_aux = reverse-last-date-received
set strict_threads = no
set uncollapse_jump = yes

# Pager behaviour
set pager_index_lines = 8      # mini index above the pager
set pager_context     = 3
set pager_stop        = yes    # don't jump to the next message at the end
set markers           = no     # no + on wrapped lines
set mark_old          = no     # unread stays "new" until read
set menu_scroll       = yes

# HTML mail: view via a text renderer, prefer text/plain when both exist
auto_view text/html
alternative_order text/plain text/enriched text/html

Plus, in ~/.mailcap:

text/html; w3m -I %{charset} -T text/html -dump; copiousoutput; nametemplate=%s.html
text/html; firefox %s; nametemplate=%s.html

The first entry (with copiousoutput) is used for inline display. The second is used when you explicitly open the attachment from the attachment menu (v, then Enter). lynx -dump and elinks -dump work just as well as w3m.

Composing and replying

set editor      = "vim +/^$ ++1"   # open the editor below the headers
set edit_headers = yes              # edit To/Cc/Subject inside the editor
set fast_reply  = yes               # skip the To/Subject prompts on reply
set include     = yes               # quote the original
set forward_format = "Fwd: %s"
set attribution = "On %d, %n wrote:"
set reply_to    = yes               # honour Reply-To without asking
set askcc       = no
set askbcc      = no
set send_charset = "us-ascii:utf-8"

# Reply from whichever of your addresses the message was sent to
alternates '^you@domain\.tld$' '^you@work\.tld$'
set reverse_name = yes

Signatures

set signature = "~/.config/mutt/signature"
set sig_dashes = yes      # prepend the standard "-- " separator line

Keep the signature plain text and four lines or fewer. Mutt sends plain-text mail, so HTML markup in a signature file arrives as literal tags. Leave out anything you wouldn't put on a public web page (a personal phone number, for example): signatures end up on mailing list archives forever.

Your Name
https://domain.tld

Address book

Built-in aliases are enough for most people:

set alias_file = "~/.config/mutt/aliases"
set sort_alias = alias
set reverse_alias = yes          # show the alias name instead of the address in the index
source $alias_file

Press a on a message to save the sender as an alias. Type the alias (or press Tab in a To: field) to complete it.

External query tools plug in through query_command. Press Q to search, or Ctrl-T in an address prompt:

# abook: an ncurses address book
set query_command = "abook --mutt-query '%s'"
macro index,pager A "<pipe-message>abook --add-email-quiet<return>" "add sender to abook"

# lbdb: aggregates many sources (aliases, LDAP, khard, past mail...)
set query_command = "lbdbq '%s'"

lbdb's m_inmail source learns addresses from mail you receive. It's normally fed by a procmail rule at delivery time, which doesn't happen with IMAP. A periodic job that feeds recently synced messages to lbdb-fetchaddr works instead:

# cron or a systemd timer, against a local Maildir (e.g. kept in sync by mbsync)
find ~/Mail/*/INBOX -type f -mtime -7 -exec sh -c 'lbdb-fetchaddr -a < "$1"' _ {} \;

The sh -c '… < "$1"' _ {} construction is the useful trick: find passes each filename as $1 (with _ filling $0), so each message is redirected to the command's stdin, which is where lbdb-fetchaddr expects it.

Mailboxes, the sidebar and handy bindings

# Folders to watch for new mail (in addition to imap_check_subscribed)
mailboxes +INBOX +Lists +Alerts

# Sidebar (built into Mutt since 1.7)
set sidebar_visible = yes
set sidebar_width   = 28
set sidebar_short_path = yes
set sidebar_folder_indent = yes
bind index,pager \CP sidebar-prev
bind index,pager \CN sidebar-next
bind index,pager \CO sidebar-open
bind index,pager B   sidebar-toggle-visible

# Quality-of-life
bind index g  noop
bind index gg first-entry
bind index G  last-entry
bind index R  group-reply
bind index <tab> next-unread
macro index c "<change-folder>?<toggle-mailboxes>" "browse mailboxes"
macro index,pager \Ca "<save-message>+Archive<enter>" "archive message"
macro index \Cr "<tag-pattern>~N<enter><tag-prefix><clear-flag>N<untag-pattern>.<enter>" "mark all new as read"

Useful search patterns (with l to limit the view or / to search): ~N new, ~F flagged, ~f alice from alice, ~s invoice subject, ~d <7d last 7 days, ~b text body (slow over IMAP), ~= duplicates. Combine them freely: l ~N ~f github.

Sending from the command line with Mutt

Mutt makes a good scripted sender when you need attachments and correct MIME, and it behaves the same everywhere (unlike the mail variants):

mutt -s "Q3 report" -a report.pdf data.csv -- [email protected] < body.txt

# Use a specific account profile, no interactive prompts
mutt -F ~/.config/mutt/accounts/work.muttrc -s "Alert" -- [email protected] < alert.txt

# HTML body
mutt -e 'set content_type=text/html' -s "Report" -- [email protected] < report.html

Note the -- that ends the attachment list before recipients.

Offline mail: syncing to a local Maildir

Mutt's IMAP support is online-first. For fast search, offline reading, or archives you control, sync the server into a local Maildir with isync/mbsync (or OfflineIMAP), then point Mutt at the local copy:

# ~/.mbsyncrc
IMAPAccount personal
Host imap.domain.tld
User [email protected]
PassCmd "pass show email/[email protected] | head -n1"
TLSType IMAPS          # older isync releases call this SSLType

IMAPStore personal-remote
Account personal

MaildirStore personal-local
Path ~/Mail/personal/
Inbox ~/Mail/personal/INBOX
SubFolders Verbatim

Channel personal
Far :personal-remote:
Near :personal-local:
Patterns *
Create Both
SyncState *
set folder    = "~/Mail/personal"
set spoolfile = "+INBOX"
set mbox_type = Maildir

Run mbsync -a from a systemd timer. This is also the foundation for notmuch search (see NeoMutt).

Server certificates

The first time Mutt connects to a server whose certificate it can't verify against the system CA bundle, it shows the certificate and asks you to (r)eject, accept (o)nce, or (a)ccept always. "Always" appends the certificate to $certificate_file.

  • For any server with a certificate from a public CA (every major provider, or anything using Let's Encrypt), you should never see this prompt. If you do, someone may be intercepting the connection, or the hostname in folder doesn't match the certificate. Treat it as a real warning.
  • For a private server with a self-signed certificate, accepting once into certificate_file is acceptable. Expect to re-accept whenever the certificate is renewed. An old pinned certificate that expired years ago just triggers the prompt again; delete it from the file.
  • Better still: give the server a real certificate, or add your private CA to the system trust store (/etc/pki/ca-trust/source/anchors/ + update-ca-trust on RHEL, /usr/local/share/ca-certificates/ + update-ca-certificates on Debian), so no pinning is needed.

Debugging

mutt -v          # version and compile-time options
mutt -D          # dump every variable's current value after reading the config
mutt -n -F /dev/null   # start with NO config (rules out your muttrc)
mutt -d 2        # write a debug log to ~/.muttdebug0 (levels 1–5)

Debug logs at level 3 and above can contain credentials and message contents. Delete them when you're done.

Use :set ?variable inside Mutt to see a single value, and :set variable to try a change live without restarting.

Error Meaning / fix
SASL authentication failed Wrong password, or the provider requires an app password or OAuth2 (Gmail, Microsoft 365)
No authenticators available SASL plugins are missing: install libsasl2-modules / cyrus-sasl-plain
Could not negotiate TLS connection Wrong port/scheme pairing (smtps:// on 587, or smtp:// on 465), or very old TLS on the server
Certificate host check failed The hostname in folder/smtp_url isn't on the certificate. Use the name the certificate was issued for
Mailbox is read-only Another client holds the mailbox or the IMAP ACL forbids writing; check imap_user
Sent mail appears twice Both record and the server are saving copies; set record = ""
Mail goes out from the wrong address A multi-account hook left an old from/smtp_url; see Approach B's pitfall
Very slow folder opens header_cache isn't set, or its directory doesn't exist