Skip to content

Connect Your Corporate Mailbox via DavMail (Recommended)

If your mailbox is corporate Exchange / Microsoft 365, we recommend using DavMail as MailAgent’s mailbox source instead of the default AppleScript backend. It is faster, more stable, and actually unlocks rich-text Reply All, thread folding, multi-folder sync, and direct calendar reads.

This page covers DavMail from scratch: what it is, why, how to install it, how to authenticate, how to confirm it, and how to daemonize it.

DavMail is an open-source email protocol gateway (a Java program). It translates Microsoft Exchange’s proprietary protocols (EWS / Graph) into standard IMAP / SMTP / CalDAV / LDAP, exposing a set of local ports on your machine:

Mail.app / Outlook proprietary path DavMail path
───────────────────────────────── ───────────────────────────────────────
backend → AppleScript → Mail.app backend → local IMAP/SMTP → DavMail → Exchange
(drives the GUI, ~1 s per email) (standard protocols, ~236 ms per email)

For MailAgent, enabling DavMail means the whole mailbox-access chain no longer depends on the Mail.app GUI—the backend fetches over standard IMAP, sends over SMTP, and reads the calendar over CalDAV. DavMail stays resident in the background, maintaining the OAuth session with the Exchange server.

DimensionDavMail (recommended)AppleScript (fallback)
Fetch speed~236 ms per email (IMAP UID FETCH)~1 s per email (drives Mail.app)
GUI dependencyNone—Mail.app need not be open/foregroundRequires Mail.app signed in and running
Rich-text Reply AllWorks properly: multipart/alternative + In-Reply-To, folds into the original conversation on the Outlook sideGUI injection is limited; rich text / thread folding is unreliable
Multi-folder syncSupported (selected custom Exchange folders join the main pipeline)Not supported
Direct CalDAV calendar readSupported (reads the Outlook server-side calendar directly)Not supported
System permissionsAlmost no macOS Automation / Accessibility permissions neededNeeds the full set: Full Disk Access + Automation + Accessibility, etc.
Cross-platformStandard protocols, paves the way for a future Linux deploymentLocked to macOS + Mail.app

In one line: Exchange users get a clearly better experience with DavMail. AppleScript remains the zero-extra-component, always-available fallback—change MAILAGENT_BACKEND back to applescript at any time to switch back.

  • A Java runtime (JRE/JDK 8+). Verify with java -version—if it prints a version, you are good. If not, brew install openjdk (no sudo needed on macOS), or use DavMail’s bundled davmail azul to fetch an embedded JRE.
  • DavMail 6.7.0 (the jar). This repo ships it at davmail-poc/jar/davmail.jar; you can also download it from davmail.sourceforge.net.
  • A corporate Exchange / Microsoft 365 mailbox, on a tenant that allows OAuth sign-in (see the broker-check note in Step 2).

DavMail uses a .properties file to describe its listening ports, OAuth mode, and credential path. Write the following into davmail-poc/config/davmail.properties:

# ===== O365 OAuth2 mode =====
# O365Manual: on startup, prints an OAuth URL; you go through MFA in the browser
# and paste the redirect URL back into stdin.
davmail.mode=O365Manual
davmail.url=https://outlook.office365.com/EWS/Exchange.asmx
# OAuth token persistence path (use an absolute path)
davmail.oauth.tokenFilePath=/absolute/path/MailAgent/davmail-poc/token/token.dat
# ===== Impersonate the Outlook for Windows well-known client_id =====
# Sign in with the Outlook desktop public client_id to get past the
# "third-party apps need IT approval" hurdle.
davmail.oauth.clientId=d3590ed6-52b3-4102-aeff-aad2292ab01c
davmail.oauth.redirectUri=urn:ietf:wg:oauth:2.0:oob
# ===== Listening ports (IMAP + SMTP + CalDAV) =====
davmail.imapPort=1143
davmail.smtpPort=1025
davmail.caldavPort=1080
davmail.ldapPort=0
davmail.popPort=0
# ===== Server mode (no tray icon, suited for a background daemon) =====
davmail.server=true
davmail.disableUpdateCheck=true
# Local plaintext communication (stays within the host), TLS off
davmail.smtpStartTls=false
davmail.imapStartTls=false

Those two client_id lines are the key; the next step explains why they are filled in this way.

Step 2: First-time OAuth authentication (impersonating the Outlook client_id)

Section titled “Step 2: First-time OAuth authentication (impersonating the Outlook client_id)”

DavMail’s built-in default client_id is its own self-registered third-party app, which most corporate tenants treat as a standalone third-party app requiring IT approval. The Outlook for Windows public client_id (d3590ed6-52b3-4102-aeff-aad2292ab01c), on the other hand, is Microsoft’s official desktop client and is usually already trusted in the tenant—signing in with it gets you an EWS token directly, bypassing the approval hurdle.

Run DavMail once in the foreground and watch the console:

Terminal window
cd ~/Documents/MailAgent/davmail-poc/jar
java -jar davmail.jar ../config/davmail.properties
  1. The console prints an OAuth URL (you can see client_id=d3590ed6-... and your login_hint inside it).
  2. Copy the URL → open it in a browser → enter your corporate account + MFA.
  3. Microsoft may show a broker check: “Are you trying to sign in to Microsoft Office? Only continue if you downloaded the app from a store or website you trust.”—click Continue.

    This happens because the client_id corresponds to Outlook for Windows, but the current process is not real Outlook (it has no Microsoft device signature), triggering an app-authenticity double check. Being able to click “Continue” through it is precisely the sign that your tenant does not enforce a device-bound check on this client_id.

  4. After Continue, you land on a perpetually loading blank page (urn:ietf:wg:oauth:2.0:oob is a redirect style modern browsers no longer support natively, so the page hangs—this is expected).
  5. Extract the authorization code from the stuck page: open the browser DevTools → Network tab → find the last failed/pending request → copy the code=... parameter from its Request URL.
  6. Paste the full code=... back into DavMail’s console stdin and press Enter.
  7. DavMail exchanges that code for an access token, establishes a session with Exchange, and writes the credentials to token/token.dat (~2 KB). The refresh token is valid for 90 days by default, so restarts after that don’t need re-authorization.

When the console stops asking for authorization and token.dat is created, authentication succeeded. You can Ctrl-C to stop this foreground run; Step 5 turns it into a daemon with PM2.

Desktop App users: fill in the DavMail settings (mailbox address, ports, Cipher Key) in the App’s Settings panel—no need to edit .env manually.

Developers running the backend from source: back in ~/Documents/MailAgent/.env, switch the backend to davmail and add a few values (each parameter maps one-to-one to the App’s Settings panel):

Terminal window
MAILAGENT_BACKEND=davmail
DAVMAIL_USER=[email protected] # usually the same as USER_EMAIL
DAVMAIL_CIPHER_KEY=any-fixed-string # token encryption key, see warning below
DAVMAIL_IMAP_PORT=1143 # must match davmail.properties
DAVMAIL_SMTP_PORT=1025
DAVMAIL_ROOT=/absolute/path/MailAgent/davmail-poc # absolute path required for the packaged App

When running from source, restart mail-sync to pick up the new backend:

Terminal window
pm2 restart mail-sync
Terminal window
# 1) Are the ports up (IMAP / SMTP)?
nc -zv localhost 1143 # IMAP, expect succeeded
nc -zv localhost 1025 # SMTP, expect succeeded
# 2) No auth errors in the DavMail log
tail -n 30 ~/Documents/MailAgent/davmail-poc/logs/davmail.log
# 3) The token is on disk and recent
ls -la ~/Documents/MailAgent/davmail-poc/token/token.dat
# 4) Test that IMAP login actually works (replace KEY with your DAVMAIL_CIPHER_KEY)
python3 - <<'PY'
import imaplib
m = imaplib.IMAP4("localhost", 1143)
m.login("[email protected]", "KEY") # password is DAVMAIL_CIPHER_KEY
print(m.list()[0]) # OK means the auth chain is working
m.logout()
PY

Then confirm the MailAgent backend is healthy overall:

Terminal window
mailagent admin health -o json | jq .data.healthy # expect true
tail -f logs/sync.log # there should be no ERROR lines

DavMail needs to stay running in the background for MailAgent to send and receive at any time. Use PM2 to keep it resident, start on boot, and restart on crash. Daemonize the java process directly (don’t rely on a foreground terminal):

Terminal window
pm2 start "$(which java)" --name davmail-poc -- \
-Xmx512M -Dsun.net.inetaddr.ttl=60 \
-jar ~/Documents/MailAgent/davmail-poc/jar/davmail.jar \
~/Documents/MailAgent/davmail-poc/config/davmail.properties
pm2 save # persist the process list
pm2 startup # run the printed command once to enable start-on-boot

Common maintenance commands:

Terminal window
pm2 status # check whether davmail-poc is online
pm2 logs davmail-poc # tail DavMail output
pm2 restart davmail-poc # restart (after editing properties)

The refresh token in token.dat is valid for 90 days by default and DavMail renews it automatically, so normally you don’t touch it. But if your device is removed from the tenant by an administrator, the refresh token is permanently invalidated (the log shows AADSTS700003)—automatic renewal can’t recover it, and you must redo the interactive authentication from Step 2: delete the old token.dat, run DavMail in the foreground once to re-authorize, then pm2 restart davmail-poc.

Alternative daemon methods: DavMail 6.7’s bundled davmail launcher script (davmail <properties path> -notray) or the community Docker image also work, but daemonizing the native java process with PM2 is the method validated in this project.

Once DavMail is working, the next step is In-App First-Time Setup—fill in your keys, choose an AI backend inside the App, and you’re ready for daily use.


Learn more: (Developer) Run the Backend from Source · Architecture Core (Sprint 16 dual-backend) · EWS Shutdown Migration Roadmap §5.1