{
  "$type": "site.standard.document",
  "content": "---\ntitle: \"The great 2025 email yak-shave: O365 + mbsync + mu + neomutt + msmtp\"\ndescription: \"Rebuilding a terminal email setup with OAuth2, Office365 and neomutt in\n  Zed---the hard-won config so you don't have to waste as many hours as I did.\"\ntags:\n  - dev\n---\n\nFor years I was a happy user of\n[mu4e](https://www.djcbsoftware.nl/code/mu/mu4e.html) in Emacs. But then a few\nyears ago my employer turned off password-based IMAP auth and broke my (Office\n365-based) work email, so I had to make alternative email arrangements.\n\nI've recently rebuilt my entire email setup around neomutt (in Zed's built-in\nterminal). I always knew that there was _some_ way to do the Office365 OAuth2\ndance and hook things back up, so I took the plunge and shaved the email yak\nagain. Here, dear reader, are the results---may you not waste as many hours\nmessing around as I did.\n\nThe new setup is mbsync (built from source with SASL support) doing IMAP sync\nwith OAuth2, with the cyrus-sasl-xoauth2 mbsync plugin handling the OAuth\ndance itself; mu for fast email search and indexing; neomutt as the email\nclient; msmtp for SMTP sending; and macOS Keychain for secure token storage.\nEach tool does one thing well, which is the Unix way---even if it means more\nconfiguration files to maintain.\n\nThe gnarliest part was getting OAuth2 working with Office365. You need to use\nthe `mutt_oauth2.py` script with Thunderbird's client ID\n(`9e5f94bc-e8a4-4e73-b8be-63364c29d753`) and the devicecode flow, since\nlocalhostauthcode doesn't work with the way my O365 exchange server is set up.\n\nHere's a snippet from my mbsyncrc showing how the OAuth token gets passed:\n\n```\nIMAPAccount anu\nHost outlook.office365.com\nPort 993\nAuthMech XOAUTH2\nUser ben.swift@anu.edu.au\nPassCmd \"/Users/ben/.dotfiles/mail/mutt_oauth2.py \\\n  --decryption-pipe 'security find-generic-password -a ben.swift@anu.edu.au -s mutt_oauth2_anu -w' \\\n  --encryption-pipe '/Users/ben/.dotfiles/mail/keychain-store.sh ben.swift@anu.edu.au mutt_oauth2_anu' \\\n  /Users/ben/.dotfiles/mail/anu_oauth2_keychain_stub\"\n```\n\nThe `keychain-store.sh` wrapper script ensures tokens are stored securely in\nmacOS Keychain rather than sitting around in plaintext files. If you're on Linux\nyou can swap my macOS-specific approach for suitable `pass` or `gpg`\ninvocations.\n\n:::tip\n\nI needed to build mbsync and the cyrus-sasl-xoauth2 plugin from source with\nXOAUTH2 support (something I plan to upstream to the homebrew formula when I get\na chance).\n\n:::\n\nRunning neomutt inside Zed is a nice touch, if you're already living there. I\nrun it in a fullscreen terminal task (same approach\nas my [Claude Code setup](/blog/2025/07/23/running-claude-code-within-zed/)).\nAdd this to your tasks.json:\n\n```json\n{\n  \"label\": \"mutt\",\n  \"command\": \"neomutt\",\n  \"reveal\": \"always\",\n  \"use_new_terminal\": true,\n  \"allow_concurrent_runs\": false\n}\n```\n\nI bind this task to a keyboard shortcut, then I'm one key command away from a\nfullscreen email client with all the Zed terminal niceties.\n\nYes, it was a yak-shave. But the payoff is real: full control over my email\nworkflow; lightning-fast search with mu (I tried notmuch, but the mu setup\nlets me use normal IMAP folders, which matters because I check email from\nmultiple devices); OAuth2 working seamlessly with Office365; everything\nrunning in my preferred editor; and the tantalising prospect of replacing\n_all_ the email parts of my job with a series of shell scripts (and Claude\nCode invocations).\n\nFor the full config files and detailed setup instructions, check out\n[my full email config on GitHub](https://github.com/benswift/.dotfiles/tree/main/mail/).\nFair warning: you'll probably need to tweak things for your specific setup, but\nthat's half the fun.\n",
  "createdAt": "2026-05-13T23:14:43.687Z",
  "description": "Rebuilding a terminal email setup with OAuth2, Office365 and neomutt in Zed---the hard-won config so you don't have to waste as many hours as I did.",
  "path": "/blog/2025/09/12/the-great-2025-email-yak-shave-o365-mbsync-mu-neomutt-msmtp",
  "publishedAt": "2025-09-12T00:00:00.000Z",
  "site": "at://did:plc:tevykrhi4kibtsipzci76d76/site.standard.publication/self",
  "tags": [
    "dev"
  ],
  "textContent": "Rebuilding a terminal email setup with OAuth2, Office365 and neomutt in Zed---the hard-won config so you don't have to waste as many hours as I did.",
  "title": "The great 2025 email yak-shave: O365 + mbsync + mu + neomutt + msmtp"
}