Skip to main content

Agent-friendly Microsoft 365 CLI — email, calendar, and more via the Graph API

Project description

m365-cli

Agent-friendly Microsoft 365 CLI — read and manage Outlook email, calendar, and people lookups via the Microsoft Graph API. All commands emit structured JSON to stdout; errors emit JSON to stderr with a non-zero exit code, so the tool is usable both by humans and by scripts/agents.

Status: alpha (0.1.2). Surface area and JSON shapes may change.

Requirements

  • Python 3.11+
  • An Azure AD app registration in the tenant you want to access (Client ID + Tenant ID). A client secret is only required for the headless client_credentials mode.

Install

pip install m365-cli

After install you'll have an m365 command on your PATH.

Azure app registration (one-time)

  1. Azure Portal → App registrations → New registration.
  2. Supported account types: pick what matches your tenant (single-tenant for most users; "common" works for personal multi-tenant).
  3. After creation, go to Authentication → Advanced settings and set Allow public client flows = Yes (required for the device-code flow).
  4. Go to API permissions → Add a permission → Microsoft Graph → Delegated and add: User.Read, Mail.Read, Mail.ReadWrite, Mail.Send, Calendars.Read, Calendars.ReadWrite, People.Read. Grant admin consent if your tenant requires it.
  5. Copy the Application (client) ID and Directory (tenant) ID — you'll paste them into the setup wizard.

For the headless client_credentials mode you'll also need a client secret and Application (not Delegated) Graph permissions, with admin consent.

Configure

Run the interactive wizard (writes a .env in the current directory):

m365 setup

Then authenticate:

m365 auth login        # device-code flow; opens a browser
m365 auth status       # confirm who you're signed in as

Configuration is read from the following locations (later overrides earlier):

  1. ~/.m365/.env — global config written by m365 setup (works from any directory)
  2. .env in the current working directory — per-project override

Environment variables set in the shell always override both files. See .env.example for all supported keys.

Common commands

# Email
m365 email list   --limit 10 --unread-only
m365 email list   --since 2025-01-01 --until 2025-01-31   # filter by date range
m365 email search --query "invoice"                        # full-text search (server-side)
m365 email search --query "budget" --folder "Archive"     # scope to a folder
m365 email read   <message-id>
m365 email send   --to alice@example.com --subject "hi" --body "hello"
m365 email reply  <message-id> --body "thanks"

# Calendar
m365 calendar events       --start 2025-04-25 --end 2025-04-30
m365 calendar create       --subject "Sync" --start ... --end ... --attendee ...
m365 calendar availability --user a@example.com --user b@example.com --start ... --end ...

# People
m365 people search --query "alice"

# Auth
m365 auth login | logout | status | token

m365 --help lists every group; each group and command has its own --help.

email search vs email list

Command Use when…
email search --query "…" You want full-text search across subject, body, and sender. Uses Exchange Search server-side — fast on any mailbox size.
email list --since … --until … You want messages in a date range or filtered by read status.

Limitation: The Microsoft Graph API does not allow $search and $filter on the same messages request. When --query is supplied to email search, the --since, --until, and --unread-only flags are silently ignored. Use email list when you need date or read-state filtering without full-text search.

Where things live on disk

Path Purpose
~/.m365/tokens.json OAuth tokens (refresh token Fernet-encrypted), chmod 600
~/.m365/.key Fernet encryption key, chmod 600 (auto-generated on first login)
~/.m365/m365.log Rotating log file (5 MB × 3)
./.env Per-project configuration (Client ID, tenant, preferences)

Security notes

  • The refresh token is encrypted at rest with Fernet. The encryption key is stored in ~/.m365/.key next to the ciphertext with the same permissions, so the on-disk encryption is defense-in-depth, not a barrier against a local attacker who already has read access to your home directory. Treat ~/.m365/ as sensitive.
  • To make tokens portable across machines, export the auto-generated key (printed on first login) into TOKEN_ENCRYPTION_KEY in your .env.
  • m365 auth token prints the raw bearer access token to stdout — use it only when you intend to pipe it into another tool.
  • client_credentials mode requires MICROSOFT_CLIENT_SECRET. Never commit it. .env, *.key, *.pem, and *.p12 are gitignored.
  • Logs in ~/.m365/m365.log may contain Graph request metadata (URLs, status codes) but tokens are truncated before being logged.

Exit codes

Code Meaning
0 success
1 error
2 authentication required (run m365 auth login)
3 resource not found

Development

git clone git@github.com:goetzcj/m365-cli.git
cd m365-cli
uv sync --extra dev      # or: pip install -e ".[dev]"
pytest

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

m365_cli-0.1.2.tar.gz (35.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

m365_cli-0.1.2-py3-none-any.whl (39.2 kB view details)

Uploaded Python 3

File details

Details for the file m365_cli-0.1.2.tar.gz.

File metadata

  • Download URL: m365_cli-0.1.2.tar.gz
  • Upload date:
  • Size: 35.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for m365_cli-0.1.2.tar.gz
Algorithm Hash digest
SHA256 a06ec951e72a4259f6dc8dca3da95bbfcdf2a5fc1d60e76dec87b84c74f0804d
MD5 7764426f70bbf100d68d3b1bef82ddd6
BLAKE2b-256 47aaf3d3b6a4fe9647a0cc3f06ad2d4c6b9e4cf96690ca43920edabb61721845

See more details on using hashes here.

File details

Details for the file m365_cli-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: m365_cli-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 39.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for m365_cli-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 b5d98f567f4910a564c7aad3db1962184f03abcd60912ab015bdddf9b1c92d5e
MD5 249144ec9b5ea8b31ba26621e0c35b29
BLAKE2b-256 9967e5cd3ffa716fb4669b7f88f5ccd9ab3203749aaf4ef925e90f6f70202419

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page