home blog

Configuring Local-based neomutt

So that we can choose to access the emails without Internet connection.
published on 2025-07-26

Nowadays almost all email providers are using applications on mobile devices and websites/applications on computers, but on Linux, what we can choose are mostly websites. However, what if we want to access our emails when we’re not connected to the Internet? It is not quite reasonable that we can only read emails through the websites or apps provided. So, is there a minimal tool that run in terminals, and also accepts local emails? Is there a minimal cli tool that synchronizes emails both-way from cloud to local and from local to cloud? Here comes the neomutt and mbsync.

Note that all the instructions below are for Outlook.

Before We Can Configure Anything

Some of the email providers asks for using OAuth to get the IMAP password, so another script is needed: mutt_oauth2.py. mutt_oauth2.py is provided by neomutt the package, usually located at /usr/share/neomutt/oauth2/mutt_oauth2.py.

$ /usr/share/neomutt/oauth2/mutt_oauth2.py -h
usage: mutt_oauth2.py [-h] [-v] [-d] [-a] [--authflow AUTHFLOW] [--format {token,sasl,msasl}] [--protocol {imap,pop,smtp}] [-t] [--decryption-pipe DECRYPTION_PIPE] [--encryption-pipe ENCRYPTION_PIPE] [--client-id CLIENT_ID]
                      [--client-secret CLIENT_SECRET] [--provider {google,microsoft}] [--email EMAIL]
                      tokenfile

positional arguments:
  tokenfile             persistent token storage

options:
  -h, --help            show this help message and exit
  -v, --verbose         increase verbosity
  -d, --debug           enable debug output
  -a, --authorize       manually authorize new tokens
  --authflow AUTHFLOW   authcode | localhostauthcode | devicecode
  --format {token,sasl,msasl}
                        output format: token - plain access token (default); sasl - base64 encoded SASL token string for the specified protocol [--protocol] and user [--email]; msasl - like sasl, preceeded with the SASL method
  --protocol {imap,pop,smtp}
                        protocol used for SASL output (default: imap)
  -t, --test            test IMAP/POP/SMTP endpoints
  --decryption-pipe DECRYPTION_PIPE
                        decryption command (string), reads from stdin and writes to stdout, default: "gpg --decrypt"
  --encryption-pipe ENCRYPTION_PIPE
                        encryption command (string), reads from stdin and writes to stdout, suggested: "gpg --encrypt --default-recipient-self"
  --client-id CLIENT_ID
                        Provider id from registration
  --client-secret CLIENT_SECRET
                        (optional) Provider secret from registration
  --provider {google,microsoft}
                        Specify provider to use.
  --email EMAIL         Your email address.

This script obtains and prints a valid OAuth2 access token. State is maintained in an encrypted TOKENFILE. Run with "--verbose --authorize --encryption-pipe 'foo@bar.org'" to get started or whenever all tokens have expired, optionally with "--
authflow" to override the default authorization flow. To truly start over from scratch, first delete TOKENFILE. Use "--verbose --test" to test the IMAP/POP/SMTP endpoints.

Checking its help manual we can see that, this script accepts several email providers, email protocols and formats, and the encryption/decryption pipeline. What we need in this case is to run

/usr/share/neomutt/oauth2/mutt_oauth2.py -av --encryption-pipe "gpg --encrypt --default-recipient-self" ~/.cache/mutt/oauth-account1

You can see that we choose to manually authorize new tokens, -v so that we get more detailed outputs, applied the recommended encryption pipeline, and finally output the token file to ~/.cache/mutt/oauth-account1.

When the script asks for client ID, we use the client ID of Thunderbird: 9e5f94bc-e8a4-4e73-b8be-63364c29d753.

After this script finishes, we get our token file in place.

Tasting neomutt with Emails Online

# ~/.config/mutt/muttrc

source "~/.config/mutt/account1"
# ~/.config/mutt/account1

set from = "<email_address>"
set realname = "<your_name>"

set imap_user = "$from"
set imap_authenticators = "xoauth2"
set imap_oauth_refresh_command = "/usr/share/neomutt/oauth2/mutt_oauth2.py ~/.cache/mutt/oauth-account1"

set smtp_authenticators = "$imap_authenticators"
set smtp_oauth_refresh_command = "$imap_oauth_refresh_command"
set smtp_url = "smtp://$imap_user@smtp.office365.com:587"

set ssl_force_tls = yes
set ssl_starttls = yes

###

set folder = "imap://outlook.office365.com"
set spoolfile = "+INBOX"

You can see that for the imap_oauth_refresh_command and smtp_oauth_refresh_command, we are running that script in a different way, that is to get the IMAP/SMTP password out through the token file. Note that you might be prompted to input the GPG paraphrase when neomutt is fetching emails.

neomutt

Keep Them Locally

Now we can make use of the other tool, mbsync. On Arch Linux, the package name is isync, so please check this before installing.

# ~/.config/isyncrc

IMAPAccount account1
Host outlook.office365.com
Port 993
User <email_address>
PassCmd "/usr/share/neomutt/oauth2/mutt_oauth2.py ~/.cache/mutt/oauth-account1 -t"
TLSType IMAPS
AuthMechs XOAUTH2

IMAPStore account1-remote
Account account1

MaildirStore account1-local
Path ~/Documents/Mail/account1/
Inbox ~/Documents/Mail/account1/INBOX/
SubFolders Verbatim

Channel account1
Far :account1-remote:
Near :account1-local:
Patterns *
Expunge both
CopyArrivalDate yes
Create both
Sync All
SyncState *

With this configuration, mbsync will store/read mails from ~/Documents/Mails/, authenticate through mutt_oauth2.py, and sync mails both-way. You can run mbsync account1 to sync explictly account1 or run mbsync -a to sync all your accounts.

Before you run neomutt again, there’re some changes you need to apply to its config file.

# ~/.config/mutt/account1

...

###

set folder = "~/Documents/Mail/account1" # neomutt now read mails from this folder
unmailboxes *
mailboxes "=INBOX" "=Drafts" "=Sent" "=Junk" "=Deleted" "=Archive"
set spoolfile = "+INBOX"
set trash = "+Deleted"
set postponed = "+Drafts"

If you have already done mbsync -a, you should be able to check all your emails after you enter neomutt. Congratulations!

Furthermore, you can use some tool to run mbsync -a every 20 minutes to keep your local emails updated. Also, neomutt is a very customizable TUI email client, you can run man 5 neomuttrc to custromize its layout, colorscheme and key-bindings to make it easy to use for you. Here are also some resources for reference: