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_credentialsmode.
Install
pip install m365-cli
After install you'll have an m365 command on your PATH.
Azure app registration (one-time)
- Azure Portal → App registrations → New registration.
- Supported account types: pick what matches your tenant (single-tenant for most users; "common" works for personal multi-tenant).
- After creation, go to Authentication → Advanced settings and set Allow public client flows = Yes (required for the device-code flow).
- 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. - 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):
~/.m365/.env— global config written bym365 setup(works from any directory).envin 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
$searchand$filteron the same messages request. When--queryis supplied toemail search, the--since,--until, and--unread-onlyflags are silently ignored. Useemail listwhen 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/.keynext 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_KEYin your.env. m365 auth tokenprints the raw bearer access token to stdout — use it only when you intend to pipe it into another tool.client_credentialsmode requiresMICROSOFT_CLIENT_SECRET. Never commit it..env,*.key,*.pem, and*.p12are gitignored.- Logs in
~/.m365/m365.logmay 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a06ec951e72a4259f6dc8dca3da95bbfcdf2a5fc1d60e76dec87b84c74f0804d
|
|
| MD5 |
7764426f70bbf100d68d3b1bef82ddd6
|
|
| BLAKE2b-256 |
47aaf3d3b6a4fe9647a0cc3f06ad2d4c6b9e4cf96690ca43920edabb61721845
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b5d98f567f4910a564c7aad3db1962184f03abcd60912ab015bdddf9b1c92d5e
|
|
| MD5 |
249144ec9b5ea8b31ba26621e0c35b29
|
|
| BLAKE2b-256 |
9967e5cd3ffa716fb4669b7f88f5ccd9ab3203749aaf4ef925e90f6f70202419
|