Outlook Email
Read, search, and manage emails via Microsoft Graph API.
Sending Emails — Draft Mode
The Graph API token lacks Mail.Send scope (your organization's Azure AD policy), so sending is done via draft creation:
outlook_send.py creates a draft in Drafts folder — user clicks Send in Outlook
outlook_reply.py creates a reply/forward draft — user clicks Send in Outlook
- The draft's
webLink opens directly in Outlook Web for quick sending
Setup (run FIRST — every time, before any operation)
You MUST complete this setup before running any script. Do NOT skip this step.
sig status ms-graph 2>&1
Check the JSON output fields configured and valid:
configured: false → run Provider Setup below. Do NOT proceed without completing it.
valid: false (but configured: true) → run sig login ms-graph, then re-check.
valid: true → detect proxy (see below), then execute the user's request.
Provider Setup
- Read
<SKILL_DIR>/references/provider-config.yaml
- Append the provider block to
~/.sig/config.yaml under providers:
- Ask the user: "Do you need a proxy to access this site?" — if yes, add
networkProxy: <url> under the provider in config.yaml
- Run
sig login ms-graph (with --network-proxy <url> if proxy was specified)
- Verify: run
sig status ms-graph again — must show valid: true before proceeding
Proxy Detection (after provider is valid)
grep -A15 "^\s*ms-graph:" ~/.sig/config.yaml | grep networkProxy | awk '{print $2}'
If this outputs a URL, prefix ALL python3 commands with HTTPS_PROXY=<url> HTTP_PROXY=<url>.
If using socks5, convert to socks5h for python (e.g. socks5://... → socks5h://...).
If empty, no proxy needed.
Running Scripts
All scripts require setup to be completed first (see above).
Scripts Reference
All scripts are in this skill's scripts/ directory. Run via Bash tool.
| Script | Purpose | Token |
|---|
outlook_folders.py | List mail folders | Graph |
outlook_messages.py | List messages from a folder | Graph |
outlook_read.py | Read full message by ID | Graph |
outlook_search.py | Search emails with KQL | Graph |
outlook_send.py | Send a new email | Graph |
outlook_reply.py | Reply/ReplyAll/Forward | Graph |
outlook_manage.py | Mark read/unread, move, delete, flag | Graph |
outlook_attachments.py | List/download attachments | Graph |
outlook_folders.py
--graph-token TOKEN Graph API Bearer token (required)
--include-children Also list child folders (flag)
outlook_messages.py
--graph-token TOKEN Graph API Bearer token (required)
--folder FOLDER Folder name or ID (default: Inbox)
--limit N Max messages (default: 20, max: 50)
--since ISO_DATE Only messages after this date
--until ISO_DATE Only messages before this date
--unread-only Only show unread messages (flag)
Well-known folder names: Inbox, SentItems, Drafts, DeletedItems, Archive, JunkEmail.
outlook_read.py
--graph-token TOKEN Graph API Bearer token (required)
--message-id ID Message ID (required)
--format FORMAT "text" (default) or "html"
outlook_search.py
--graph-token TOKEN Graph API Bearer token (required)
--query TEXT KQL search query (required)
--folder FOLDER Restrict to folder (optional)
--limit N Max results (default: 20, max: 50)
outlook_send.py
--graph-token TOKEN Graph API Bearer token (required)
--to ADDRESSES Comma-separated recipient emails (required)
--subject TEXT Email subject (required)
--body TEXT Email body content (required)
--cc ADDRESSES Comma-separated CC addresses
--bcc ADDRESSES Comma-separated BCC addresses
--body-type TYPE "text" (default) or "html"
--importance LEVEL "low", "normal" (default), or "high"
--no-save-to-sent Do not save to Sent Items
outlook_reply.py
--graph-token TOKEN Graph API Bearer token (required)
--message-id ID Original message ID (required)
--action ACTION "reply", "replyAll", or "forward"
--body TEXT Reply/forward body content (required)
--to ADDRESSES Recipient addresses (required for forward)
outlook_manage.py
--graph-token TOKEN Graph API Bearer token (required)
--message-id ID Message ID (required)
--action ACTION "read", "unread", "move", "delete", "flag", "unflag"
--folder FOLDER Destination folder (required for move)
outlook_attachments.py
--graph-token TOKEN Graph API Bearer token (required)
--message-id ID Message ID (required)
--download ATTACHMENT_ID Download a specific attachment (optional)
--output-dir DIR Directory for downloaded file (default: /tmp)
Safety
Always show the user the draft (recipients, subject, body preview) and get explicit confirmation before calling outlook_send.py, outlook_reply.py, or outlook_manage.py --action delete.
KQL Search Syntax
Use KQL (Keyword Query Language) with outlook_search.py:
"quarterly report" # Subject, body, or attachment name
from:jane.doe@example.com # From a specific sender
to:john@example.com # Sent to someone
subject:"weekly standup" # In subject line
hasattachment:true # Has attachments
received>=2026-04-01 # Received after date
from:jane subject:report # Combined filters
Note: $orderby is not supported with $search — results are returned by relevance.
Key Concepts
Folder names — Use well-known names (Inbox, SentItems, Drafts, DeletedItems, Archive, JunkEmail) or folder IDs from outlook_folders.py.
Message IDs — Long opaque strings (e.g., AAMkAD...). Get from outlook_messages.py or outlook_search.py, then use with outlook_read.py, outlook_reply.py, outlook_manage.py, outlook_attachments.py.
Conversation threading — Each message has a conversationId. To see a full email thread: read one message, note its conversationId, then search/filter for other messages in that conversation.
HTML bodies — outlook_read.py converts HTML to plain text by default. Use --format html to get the raw HTML.
Time parameters — Accept both YYYY-MM-DD and full ISO 8601 (2026-04-13T09:00:00Z).
Error Handling
| Error | Cause | Fix |
|---|
| 401 Unauthorized | Token expired | Auto-run sig login (no user prompt needed), retry |
| 403 Forbidden | Insufficient permissions | Check Graph API permissions (Mail.Read, Mail.Send) |
| 404 Not found | Invalid message/folder ID | Verify ID from a list or search |
| 429 Too Many Requests | Rate limited | Wait and retry |
MISSING_ARGS | Required arguments missing | Check script --help |
Workflow Examples
Check inbox (recent unread emails)
sig run ms-graph -- bash -c 'python3 scripts/outlook_messages.py --graph-token "$SIG_MS_GRAPH_ACCESS_TOKEN" --unread-only --limit 10'
Read a specific email
- List messages to find the ID:
sig run ms-graph -- bash -c 'python3 scripts/outlook_messages.py --graph-token "$SIG_MS_GRAPH_ACCESS_TOKEN" --limit 5'
- Read full content:
sig run ms-graph -- bash -c 'python3 scripts/outlook_read.py --graph-token "$SIG_MS_GRAPH_ACCESS_TOKEN" --message-id "$MSG_ID"'
Search for emails
sig run ms-graph -- bash -c 'python3 scripts/outlook_search.py --graph-token "$SIG_MS_GRAPH_ACCESS_TOKEN" --query "from:jane.doe@example.com subject:report"'
Send an email
- Show draft to user and get confirmation
sig run ms-graph -- bash -c 'python3 scripts/outlook_send.py --graph-token "$SIG_MS_GRAPH_ACCESS_TOKEN" --to "jane.doe@example.com" --subject "Meeting follow-up" --body "Hi Jane, ..."'
Reply to an email
- Read the email first:
sig run ms-graph -- bash -c 'python3 scripts/outlook_read.py --graph-token "$SIG_MS_GRAPH_ACCESS_TOKEN" --message-id "$MSG_ID"'
- Show reply draft to user and get confirmation
sig run ms-graph -- bash -c 'python3 scripts/outlook_reply.py --graph-token "$SIG_MS_GRAPH_ACCESS_TOKEN" --message-id "$MSG_ID" --action reply --body "Thanks, sounds good!"'
Forward an email
- Show forward draft to user and get confirmation
sig run ms-graph -- bash -c 'python3 scripts/outlook_reply.py --graph-token "$SIG_MS_GRAPH_ACCESS_TOKEN" --message-id "$MSG_ID" --action forward --body "FYI" --to "colleague@example.com"'
Download an attachment
- List attachments:
sig run ms-graph -- bash -c 'python3 scripts/outlook_attachments.py --graph-token "$SIG_MS_GRAPH_ACCESS_TOKEN" --message-id "$MSG_ID"'
- Download:
sig run ms-graph -- bash -c 'python3 scripts/outlook_attachments.py --graph-token "$SIG_MS_GRAPH_ACCESS_TOKEN" --message-id "$MSG_ID" --download "$ATTACHMENT_ID" --output-dir ~/Downloads'
Check mail folders
sig run ms-graph -- bash -c 'python3 scripts/outlook_folders.py --graph-token "$SIG_MS_GRAPH_ACCESS_TOKEN" --include-children'