Outlook Ops
In Copilot, Outlook is a native MCP. The harness uses these tools directly — no helper scripts, no markdown-to-Outlook sync bridge needed.
What's available
Read
| Operation |
Tool |
| Search any mailbox content |
SearchM365 with sources=["email"] |
| List messages with filters |
ListMessages (folder, from, date, unread, flagged) |
| Get a specific message |
GetMessage |
| List mail folders |
ListMailFolders |
| List calendar events |
ListCalendarView / ListEvents |
| Find free/busy slots |
FindMeetingTimes |
| Search SharePoint/OneDrive |
SearchM365 with sources=["files"] or SearchDrive |
Write
| Operation |
Tool |
| Draft a new email (NOT send) |
CreateDraftMessage |
| Draft a threaded reply (NOT send) |
CreateReplyDraft |
| Draft a reply-all (NOT send) |
CreateReplyAllDraft |
| Update an existing draft |
UpdateDraft |
| Send a draft |
SendDraftMessage |
| Add file attachment to a draft |
AddDraftAttachments |
In v1, the harness uses CreateDraftMessage / CreateReplyDraft only. It never calls SendDraftMessage. The human clicks Send in Outlook — that click is the approval.
Tenant configuration
From sales-harness-config.md in OneDrive:
OUTLOOK_FROM: [YOUR_OUTLOOK_EMAIL]
- Drafts folder ID:
[YOUR_DRAFTS_FOLDER_ID] (well-known name drafts works in most calls)
- Sent Items folder ID:
[YOUR_SENT_FOLDER_ID] (well-known name sentitems works in most calls)
- Inbox folder ID:
[YOUR_INBOX_FOLDER_ID] (well-known name inbox works in most calls)
Most ListMessages calls accept the well-known names directly, so you don't actually need to look up the IDs unless you want stable references. To get IDs, call mcp__outlook__ListMailFolders once and copy them into your config.
Composer flow — saving a first-touch draft
When the Composer finishes a draft and the lead's state is researched → draft_pending_approval:
- Write the draft text to the prospect MD file's
## Draft (pending approval) section (for audit + human review in OneDrive).
- Call
CreateDraftMessage:
to=[<prospect email>]
subject=<draft subject>
body=<draft body, HTML or plain text>
- Capture the returned draft
id and store it in the prospect frontmatter as draft_message_id.
- Set
state: draft_pending_approval. Append to ## Audit.
Composer flow — saving a reply draft
When the Composer is drafting a reply to a prospect's reply:
- Get the inbound message ID (from Cadence's reply detection — stored in the lead's
## Conversation section).
- Call
CreateReplyDraft(message_id=<inbound>, comment=<draft body>).
- Update the MD file and capture
draft_message_id.
Cadence flow — queueing a templated step
When next_step_at is due for a lead in awaiting_reply:
- Load the cadence template (e.g.
cadence-rules/templates/outbound-email-v1.md).
- Render the next step using lead variables.
- Run compliance checks (
compliance/SKILL.md).
- Call
CreateReplyDraft(message_id=<original thread root>, comment=<rendered body>) — follow-ups are always threaded.
- Increment
touch_count. Set state: draft_pending_approval. Append to ## Audit.
Cadence flow — detecting a send
Periodically scan for sends that have completed:
ListMessages(folder_id="sentitems", received_after=<draft created time>) — or SearchM365 with sources=["email"], from_user=<OUTLOOK_FROM>, query=<subject>.
- Match on subject + recipient + send time after the draft creation.
- If exactly one match → transition lead
draft_pending_approval → sent, set sent_at, capture thread_id from the message's conversationId, clear draft_message_id.
- If zero matches and the draft is older than 7 days → set
hold_reason: draft never sent, do not transition.
- If multiple matches → take the most recent, log a warning in
## Notes.
Cadence flow — detecting a reply
Periodically scan for replies on threads we're awaiting:
- For each lead in
awaiting_reply with a thread_id, call ListMessages(folder_id="inbox", received_after=<sent_at>) and filter by conversationId matching thread_id and sender matching the prospect's domain. Or SearchM365 with sources=["email"], from_user=<prospect email>, after=<sent_at>.
- If a match → transition to
state: replied, copy the reply body and message ID into the lead's ## Conversation section, hand off to Composer for reply drafting.
Authentication
All Graph access is delegated using the operator's M365 sign-in ([YOUR_OUTLOOK_EMAIL]). The harness never holds a service-principal token. If a tool call fails with Unauthorized, the operator needs to re-authenticate — the harness does not retry blindly.
1---2name: outlook-ops3description: How the [YOUR_FIRM] sales harness reads from and writes to Outlook via the Copilot MCP. Composer uses it to save drafts. Cadence uses it to detect sends, detect replies, and queue templated follow-ups. Use when the harness needs to draft a new email, draft a threaded reply, search sent items for a send, scan inbox for a reply, or propose meeting times.4---56# Outlook Ops78In Copilot, Outlook is a native MCP. The harness uses these tools directly — no helper scripts, no markdown-to-Outlook sync bridge needed.910## What's available1112### Read1314| Operation | Tool |15|---|---|16| Search any mailbox content | `SearchM365` with `sources=["email"]` |17| List messages with filters | `ListMessages` (folder, from, date, unread, flagged) |18| Get a specific message | `GetMessage` |19| List mail folders | `ListMailFolders` |20| List calendar events | `ListCalendarView` / `ListEvents` |21| Find free/busy slots | `FindMeetingTimes` |22| Search SharePoint/OneDrive | `SearchM365` with `sources=["files"]` or `SearchDrive` |2324### Write2526| Operation | Tool |27|---|---|28| Draft a new email (NOT send) | `CreateDraftMessage` |29| Draft a threaded reply (NOT send) | `CreateReplyDraft` |30| Draft a reply-all (NOT send) | `CreateReplyAllDraft` |31| Update an existing draft | `UpdateDraft` |32| Send a draft | `SendDraftMessage` |33| Add file attachment to a draft | `AddDraftAttachments` |3435In v1, the harness uses `CreateDraftMessage` / `CreateReplyDraft` only. It never calls `SendDraftMessage`. The human clicks Send in Outlook — that click is the approval.3637## Tenant configuration3839From `sales-harness-config.md` in OneDrive:4041- `OUTLOOK_FROM`: `[YOUR_OUTLOOK_EMAIL]`42- Drafts folder ID: `[YOUR_DRAFTS_FOLDER_ID]` (well-known name `drafts` works in most calls)43- Sent Items folder ID: `[YOUR_SENT_FOLDER_ID]` (well-known name `sentitems` works in most calls)44- Inbox folder ID: `[YOUR_INBOX_FOLDER_ID]` (well-known name `inbox` works in most calls)4546> Most ListMessages calls accept the well-known names directly, so you don't actually need to look up the IDs unless you want stable references. To get IDs, call `mcp__outlook__ListMailFolders` once and copy them into your config.4748## Composer flow — saving a first-touch draft4950When the Composer finishes a draft and the lead's state is `researched` → `draft_pending_approval`:51521. Write the draft text to the prospect MD file's `## Draft (pending approval)` section (for audit + human review in OneDrive).532. Call `CreateDraftMessage`:54 - `to=[<prospect email>]`55 - `subject=<draft subject>`56 - `body=<draft body, HTML or plain text>`573. Capture the returned draft `id` and store it in the prospect frontmatter as `draft_message_id`.584. Set `state: draft_pending_approval`. Append to `## Audit`.5960## Composer flow — saving a reply draft6162When the Composer is drafting a reply to a prospect's reply:63641. Get the inbound message ID (from Cadence's reply detection — stored in the lead's `## Conversation` section).652. Call `CreateReplyDraft(message_id=<inbound>, comment=<draft body>)`.663. Update the MD file and capture `draft_message_id`.6768## Cadence flow — queueing a templated step6970When `next_step_at` is due for a lead in `awaiting_reply`:71721. Load the cadence template (e.g. `cadence-rules/templates/outbound-email-v1.md`).732. Render the next step using lead variables.743. Run compliance checks (`compliance/SKILL.md`).754. Call `CreateReplyDraft(message_id=<original thread root>, comment=<rendered body>)` — follow-ups are always threaded.765. Increment `touch_count`. Set `state: draft_pending_approval`. Append to `## Audit`.7778## Cadence flow — detecting a send7980Periodically scan for sends that have completed:81821. `ListMessages(folder_id="sentitems", received_after=<draft created time>)` — or `SearchM365` with `sources=["email"]`, `from_user=<OUTLOOK_FROM>`, query=`<subject>`.832. Match on subject + recipient + send time after the draft creation.843. If exactly one match → transition lead `draft_pending_approval → sent`, set `sent_at`, capture `thread_id` from the message's `conversationId`, clear `draft_message_id`.854. If zero matches and the draft is older than 7 days → set `hold_reason: draft never sent`, do not transition.865. If multiple matches → take the most recent, log a warning in `## Notes`.8788## Cadence flow — detecting a reply8990Periodically scan for replies on threads we're awaiting:91921. For each lead in `awaiting_reply` with a `thread_id`, call `ListMessages(folder_id="inbox", received_after=<sent_at>)` and filter by `conversationId` matching `thread_id` and sender matching the prospect's domain. Or `SearchM365` with `sources=["email"]`, `from_user=<prospect email>`, `after=<sent_at>`.932. If a match → transition to `state: replied`, copy the reply body and message ID into the lead's `## Conversation` section, hand off to Composer for reply drafting.9495## Authentication9697All Graph access is delegated using the operator's M365 sign-in (`[YOUR_OUTLOOK_EMAIL]`). The harness never holds a service-principal token. If a tool call fails with `Unauthorized`, the operator needs to re-authenticate — the harness does not retry blindly.