Microsoft OAuth authentication with mu4e in Emacs

Posted on January 11, 2022

This post explains how to use OAuth two factor authentication (2FA) to access Microsoft Outlook email via IMAP with the mu4e email client for Emacs on Linux (and maybe MacOS, I haven't checked).

Steps

  1. Set up M365-IMAP to obtain OAuth tokens from the Microsoft identity endpoint.

  2. Configure offlineimap to obtain email using OAuth tokens generated by M365-IMAP.

  3. Configure mu4e to:

    • use offlineimap to receive email with M365-IMAP under the hood,
    • use M365-IMAP directly to send email.

Create OAuth tokens with M365-IMAP

Clone the M365-IMAP repository:

git clone https://github.com/UvA-FNWI/M365-IMAP.git
cd M365-IMAP

Follow their README.md instructions, using the "Use Thunderbird’s public client ID" option as the client ID, i.e not the "Use your own Azure AD app registration" option. Then follow the instructions in their "Obtaining auth tokens" section.

Once you have set this up, python3 get_token.py will create two short-lived files: imap_smtp_refresh_token and imap_smtp_access_token.

For mu4e, you will run python3 refresh_token.py to create a new OAuth token each time.

Configure offlineimap

First install offlineimap with these instructions.

Your .offlineimaprc file needs knowledge of a Python file that will call python3 refresh_token.py. In the .offlineimaprc file add the following in the [general] section:

pythonfile = /path/to/offlineimaptoken.py

Next create that /path/to/offlineimaptoken.py. That file should contain the following function that returns the refresh_token.py output:

import subprocess

def get_token():
  return subprocess.getoutput("cd /path/to/M365-IMAP/; python3 refresh_token.py").split()[0]

Your .offlineimaprc file should add your username and domain (replacing USER@DOMAIN.COM below), and include an oauth2_access_token_eval entry to use the get_token() function above to obtain the OAuth access token:

[Repository Remote]
type = IMAP
remoteuser = USER@DOMAIN.COM
remotehost = outlook.office365.com
remoteport = 993
auth_mechanisms = XOAUTH2
oauth2_request_url = https://login.microsoftonline.com/common/oauth2/v2.0/token
oauth2_client_id = 9e5f94bc-e8a4-4e73-b8be-63364c29d753
oauth2_access_token_eval = get_token()

Important: see the "Complete offlineimap configuration" section below for the full .offlineimaprc configuration.

Now test that offlineimap uses the get_token() function to obtain an OAuth token and checks your remote Microsoft outlook mail for new messages.

offlineimap -c /path/to/.offlineimaprc

When that works, move onto the next step.

Configure mu4e

Set up mu4e. Here's the official mu4e manual. After you've initialised your mu database of emails, it's useful to test mu on its own in a terminal window outside of Emacs, before trying to test mu4e in Emacs:

mu index

Next, set up mu4e to receive and send emails.

  1. Receiving emails

    To integrate offlineimap into your mu4e setup, you need to set the value for mu4e-get-mail-command:

    (setq mu4e-get-mail-command "offlineimap.py -c /path/to/.offlineimaprc")
    

    Now you're ready to receive emails in Emacs with mu4e, a guide is here.

    If that works, then you are using OAuth 2FA to receive Microsoft Outlook email via IMAP with the mu4e email client for Emacs.

  2. Sending emails

    To use OAuth for authentication to send emails via SMTP, below is my Elisp that you should adopt in your own Emacs init file.

    Remember to replace /path/to/M365-IMAP/ (appearing once) and USER@DOMAIN.COM (appearing twice) with your own values.

       ;;; Generate the OAuth authentication token
    (defun fetch-access-token ()
      (let ((username "USER@DOMAIN.COM")
            (m365 "/path/to/M365-IMAP/"))
        (with-temp-buffer
          (call-process-shell-command
           (format "cd  %s; printf \"user=%s\\x01auth=Bearer $(python3 refresh_token.py)\\x01\\x01\" | base64"
                   m365 username)
           nil (current-buffer) nil)
          (buffer-string))))
    
        ;;; Add new authentication method for xoauth2
        (cl-defmethod smtpmail-try-auth-method
          (process (_mech (eql 'xoauth2)) user password)
          (let* ((access-token (fetch-access-token)))
             (smtpmail-command-or-throw
              process
              (concat "AUTH XOAUTH2 " access-token)
              235)))
    
        ;;; Register the method
        (with-eval-after-load 'smtpmail
          (add-to-list 'smtpmail-auth-supported 'xoauth2))
    
       (setq message-send-mail-function 'smtpmail-send-it
             starttls-use-gnutls t
             smtpmail-starttls-credentials
             '(("smtp.office365.com" 587 nil nil))
             smtpmail-default-smtp-server "smtp.office365.com"
             smtpmail-smtp-server "smtp.office365.com"
             smtpmail-smtp-user "USER@DOMAIN.COM"
             smtpmail-stream-type  'starttls
             smtpmail-smtp-service 587
             ;; remove once things are working
             smtpmail-debug-info t
             ;; force smtpmail to use use authentication
             smtpmail-servers-requiring-authorization ".*"
             )
    

    You should now attempt to send an email with mu4e, to ensure that the above Elisp configuration works for you.

Complete offlineimap configuration

Here is my complete .offlineimaprc configuration file:

[general]
accounts = MyAccount
maxsyncaccounts = 1
pythonfile = /path/to/offlineimaptoken.py

[Account MyAccount]
localrepository = Local
remoterepository = Remote

[Repository Local]
type = Maildir
localfolders = /path/to/offlineimap-mail

[Repository Remote]
type = IMAP
remoteuser = USER@DOMAIN.COM
remotehost = outlook.office365.com
remoteport = 993
ssl = yes
auth_mechanisms = XOAUTH2
oauth2_request_url = https://login.microsoftonline.com/common/oauth2/v2.0/token
oauth2_client_id = 9e5f94bc-e8a4-4e73-b8be-63364c29d753
oauth2_access_token_eval = get_token()

folderfilter = lambda foldername: foldername in ["INBOX", "Sent Items"]
sslcacertfile = OS-DEFAULT

Notes

  1. I updated this blog on 17th September 2026. It previously provided guidance on creating your own Azure AD application for OAuth token creation. Now the guidance is to use Thunderbird’s public Azure AD application, thus avoiding the requirement to create an Azure AD application.

  2. If M365-IMAP is not creating OAuth tokens, check on the M365-IMAP website for the latest version of the Thunderbird client ID. It changes very infrequently, the client ID in this blog post was valid on 17th September 2026.