Purpose
Manage Outlook Mail messages with the outlook-mail companion CLI. The
connector supports both personal Microsoft accounts and Microsoft 365 work or
school accounts.
Tooling
Use exec to run:
outlook-mail <subcommand> [options]
Core subcommands:
disconnectaccounts(identity metadata only; never tokens)list [--page-size 10] [--unread] [--page-token 20] [--folder sentitems]get "<MESSAGE_ID>"search "<query>" [--folder sentitems] [--page-size 10]send --to alice@example.com [--to bob@example.com] [--cc charlie@example.com] --subject "Hello" --body "Hi ..." [--attachment /path/to/file.pdf]reply "<MESSAGE_ID>" --body "Thanks for the update!" [--reply-all]delete "<MESSAGE_ID>"(moves the message to Deleted Items; recoverable, not a permanent delete)mark-read "<MESSAGE_ID>"mark-unread "<MESSAGE_ID>"
Commands use the default linked mailbox unless --account <account_id> is
provided. When the user clearly means a particular mailbox, run
outlook-mail accounts, match its display_name, and pass its opaque
account_id with --account. If the match is ambiguous, ask instead of
guessing. Never silently fall back to the default after an account-selection
error. --account applies only to mailbox operations; --status, accounts,
and disconnect are connector-wide. To remove one mailbox while keeping the
others, direct the user to that account under Connectors in Settings; do not
run disconnect.
Once an account is selected, repeat the same --account <account_id> on every
follow-up that uses results from that mailbox, including pagination with
--page-token, get, reply, delete, mark-read, and mark-unread.
Message IDs and page tokens belong to the account that returned them.
For mail-finding or search requests where the mailbox is unclear, run
outlook-mail accounts first. When more than one mailbox is linked, search
each with its own --account; do not say the message is missing until every
mailbox returned no match. If a mailbox cannot be searched, say it could not
be checked and follow the reauthorization flow below instead of claiming the
message does not exist.
Use --page-size for result count. --top, --limit, and --max-results
are compatibility aliases only; do not use them in new commands. Use
get "<MESSAGE_ID>" for a single message; read, get --id, and
search --query are compatibility forms only.
JSON output contract:
disconnect: parseok,action,status, anddisconnect_urllist/search: parseok,count,next_page_token(when present, pass back as--page-token),total_messages,retrieved_at, andmessages[]withid,subject,from,to,date,message_received_at,preview,is_read, andhas_attachmentsget: parseok,retrieved_at, andmessagewithid,subject,from,to,cc,date,message_received_at,body,body_type,is_read, andhas_attachmentssend: parseokandactionreply: parseok,action, andmessage_iddelete: parseok,action(trashed), andmessage_id; the moved message gets a new id, somessage_idis not the id you passed in — use the returned one for any follow-up commandmark-read/mark-unread: parseok,action, andmessage_id
Auth
Use outlook-mail --status for connector state and link management. The binary handles its callback target internally.
- If
outlook-mail --statusreturnsconnect_url, replace<connect_url>with the returned URL and share exactly this Markdown link:[Connect Outlook Mail](<connect_url>); do not paste the raw URL separately. - When the connected response includes
add_account_url, share it as[Add Outlook Mail account](<add_account_url>)when the user wants another mailbox. - If the user wants to disconnect, run
outlook-mail disconnect. Whendisconnect_urlis present, replace<disconnect_url>with the returned URL and share exactly this Markdown link:[Disconnect Outlook Mail](<disconnect_url>); do not paste the raw URL separately. Otherwise say it is already disconnected. - Keep status and linking inside
outlook-mail --status; do not use a shared connector helper CLI. - Never print tokens, cookies, or connector secrets.
Operating Rules
- Use
searchfor targeted lookup,listfor browsing, andgetonly when you need the full body of a specific message. - Message IDs are opaque Graph values. Reuse the exact
idreturned bylistorsearch. - Before
send, confirm recipients, subject, and body in the current thread. - Before
reply, confirm the reply body and whether the user wants--reply-all. Use--reply-allonly when the user explicitly wants everyone included. delete,mark-read, andmark-unreadmay proceed from a clear user request without an additional confirmation.deletemoves the message to the Deleted Items folder, where the user can still recover it; tell the user that, and do not describe it as permanent or unrecoverable.- For mailbox-summary requests, exclude likely spam, phishing, or irrelevant bulk promotions by default unless the user explicitly asks for junk or spam, and briefly note that filtering if you used it.
- If any command reports an auth failure, stop and run
outlook-mail --status. If it returnsconnect_url, share the normal connect link. Otherwise runoutlook-mail accounts: check the selected account when the failed command used--account, or every account when it did not. For each relevant row withneeds_reauth: true, share the status response'sadd_account_urlas[Reconnect Outlook Mail account](<add_account_url>)and ask the user to sign in with that same Microsoft account. Never rerun a failed--accountcommand without--account. - Never surface raw Graph identifiers (message ids, conversation ids) or other internal response fields (change keys,
@odatafields, page/skip tokens, raw JSON) in text shown to the user — including in summaries, lists, or per-item annotations. Reuse the ids only internally to chain follow-up commands (rule 2). The sole exceptions are when the user explicitly asks for a raw id or you must show one to troubleshoot a failure. - The compatibility
datefield is Outlook's message-received time. Prefermessage_received_at.user_localwhen presenting it. It is not the time of an event described inside the email; never infer a delivery, payment, trip, meeting, or other event time from it.
- Permission-withheld fields: a
list/searchresult carrying awithheldentry had those fields removed by the user's "Access messages" permission — they are NOT empty. Answer from the remaining fields and mention that the permission hides the rest. Only when the user actually needs a withheld field for one specific message andwithheld.reasonisrequires_approval, read that message withget, which shows the user the approval prompt. Nevergetduring routine browsing just to fill previews.