Skip to content

How to Connect Claude to Microsoft 365 Outlook and Calendar Without Claude Teams

I have ADHD. My inbox is a disaster. Unread emails pile up, calendar events slip by unnoticed, and I constantly miss follow-ups. I thought Claude could help - triage my email, remind me about meetings, draft replies for urgent messages. But when I looked into it, the official solution is Claude Teams, which costs significantly more per seat. My small company won’t approve multiple paid seats for an AI assistant.

So I started digging. A Reddit thread on r/ClaudeAI gave me hope. Users were connecting Claude to Outlook without Teams. One comment (18 upvotes) described daily email triage: “Claude triages my email every morning and sorts it into Respond Now/Can Wait/Awareness/SPAM.” Another (15 upvotes) mentioned: “got it going to office365 via the graph api and an azure application.”

It turns out Claude Teams is NOT required. You can integrate Claude with Microsoft 365 using Claude Code and MCP servers. The setup is technical, but it works.

The Problem: Claude Teams Barrier

Claude Teams offers Microsoft 365 integration out of the box. But for individuals or small teams, the cost per seat is prohibitive. I just wanted email triage and calendar awareness - not a full enterprise AI deployment.

The key insight: Claude Code (the CLI version of Claude) supports MCP (Model Context Protocol) servers. An MCP server can connect Claude to any API - including Microsoft Graph API, which powers Outlook, Calendar, and all Microsoft 365 services.

Solution 1: Outlook MCP Server

The simplest approach uses an existing Outlook MCP server. This server wraps Microsoft Graph API and exposes email and calendar operations as tools Claude can call.

Step 1: Register an Azure Application

First, create an Azure app that can access Microsoft Graph:

Azure CLI setup
# Install Azure CLI if needed
brew install azure-cli
# Login to Azure
az login
# Register the application
az ad app create --display-name "Claude-Outlook-Integration" \
--oauth2-allow-implicit-flow false \
--required-resource-accesses '[{"resourceAppId":"00000003-0000-0000-c000-000000000000","resourceAccess":[{"id":"e2a3a97e-959e-43a8-b5de-1098e6b2b4c4","type":"Scope"},{"id":"a792d9f5-71d1-4e64-b837-1b2a3cd6f268","type":"Scope"}]}]'

The resourceAppId 00000003-0000-0000-c000-000000000000 is Microsoft Graph API. The two resource access IDs correspond to:

  • Mail.Read - read emails
  • Calendars.ReadWrite - manage calendar events

Step 2: Configure API Permissions

Go to the Azure Portal and add delegated permissions:

Required API permissions
Permission | What it allows
------------------------|----------------------------------
Mail.Read | Read user's emails
Mail.ReadWrite | Read and send emails (optional)
Calendars.Read | Read calendar events
Calendars.ReadWrite | Create/update events (optional)
Tasks.ReadWrite | Manage To Do tasks (optional)

I started with just Mail.Read and Calendars.Read for safety. You can add more later.

Step 3: Create a Client Secret

Create Azure app secret
# Get your app's object ID
APP_ID=$(az ad app list --display-name "Claude-Outlook-Integration" --query '[0].appId' -o tsv)
# Create a secret (expires in 1 year)
az ad app credential reset --id $APP_ID --years 1

Save the password output - this is your client secret. You’ll need it for the MCP configuration.

Step 4: Configure Claude Code MCP

Add the Outlook MCP server to your Claude Code settings:

Claude Code MCP configuration
// ~/.claude/settings.json
{
"mcpServers": {
"outlook": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-outlook"],
"env": {
"OUTLOOK_CLIENT_ID": "your-azure-app-id",
"OUTLOOK_CLIENT_SECRET": "your-client-secret",
"OUTLOOK_TENANT_ID": "common"
}
}
}
}

The tenantId of common allows any Microsoft account. Use your organization’s tenant ID if you want to restrict access.

Step 5: Authenticate

When you first use the Outlook MCP server, it prompts for OAuth authentication:

OAuth authentication flow
1. Claude calls the MCP server
2. Server opens a browser window
3. You login to Microsoft 365
4. Server receives access token
5. Token cached for future calls

After authentication, the token is cached. Future calls work without re-login.

Solution 2: Direct Graph API Integration

If the Outlook MCP server doesn’t have the features you need, you can call Graph API directly. This approach is more flexible but requires more setup.

Step 1: Same Azure App Setup

Follow the same Azure app registration steps above. But add more permissions if needed:

Graph API permission mapping
Feature | Required Permission
---------------------|----------------------
Read emails | Mail.Read
Send emails | Mail.Send
Read calendar | Calendars.Read
Create events | Calendars.ReadWrite
Read contacts | Contacts.Read
Manage tasks | Tasks.ReadWrite

Step 2: Create a Custom MCP Server

Write a simple MCP server that calls Graph API:

Custom Graph API MCP server
# mcp_servers/graph_api.py
import httpx
from mcp.server import Server
from mcp.server.stdio import stdio_server
APP_ID = "your-azure-app-id"
CLIENT_SECRET = "your-client-secret"
TENANT_ID = "common"
async def get_access_token():
"""Get OAuth token from Microsoft"""
token_url = f"https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token"
data = {
"client_id": APP_ID,
"client_secret": CLIENT_SECRET,
"scope": "https://graph.microsoft.com/.default",
"grant_type": "client_credentials"
}
response = httpx.post(token_url, data=data)
return response.json()["access_token"]
async def get_emails(token, limit=10):
"""Fetch unread emails"""
headers = {"Authorization": f"Bearer {token}"}
url = f"https://graph.microsoft.com/v1.0/me/messages?$filter=isRead eq false&$top={limit}"
response = httpx.get(url, headers=headers)
return response.json()["value"]
# Create MCP server with tools for email/calendar operations
server = Server("graph-api")
@server.tool("get_unread_emails")
async def get_unread_emails(limit: int = 10):
"""Get unread emails from Outlook"""
token = await get_access_token()
emails = await get_emails(token, limit)
return emails
# Run the server
async def main():
async with stdio_server() as (read_stream, write_stream):
await server.run(read_stream, write_stream)
if __name__ == "__main__":
import asyncio
asyncio.run(main())

This is a minimal example. A real implementation would handle token caching, error handling, and more tools.

Step 3: Configure Claude Code

Custom MCP server configuration
// ~/.claude/settings.json
{
"mcpServers": {
"graph-api": {
"command": "python",
"args": ["mcp_servers/graph_api.py"],
"env": {
"AZURE_APP_ID": "your-app-id",
"AZURE_CLIENT_SECRET": "your-secret"
}
}
}
}

Using Claude for Email Triage

Once configured, I created a skill for daily email triage:

Morning email triage skill
## Morning Email Triage
Ask Claude Code to:
1. Fetch unread emails from Outlook via MCP
2. Categorize each email:
- **Respond Now**: Client emails, deadlines, urgent requests
- **Can Wait**: Internal updates, non-urgent threads
- **Awareness**: FYI emails, newsletters I actually subscribed to
- **SPAM**: Promotions, irrelevant marketing
3. Draft replies for "Respond Now" items
4. Flag items unreplied for more than 2 days
5. Summary report with counts and action items
Example prompt:
"I need to triage my email. Fetch unread emails, categorize them by urgency,
draft replies for urgent ones, and tell me what needs action today."

Claude responds with something like:

Email triage output
## Email Triage Report - April 3, 2026
### Respond Now (3 emails)
1. **Client: Project deadline** - Needs reply about timeline
Draft: "Thanks for the update. We'll deliver by April 10..."
2. **Manager: Q2 review prep** - Schedule requested
Draft: "I'll prepare the slides by Friday. Let's meet Monday..."
3. **Invoice overdue notice** - Payment needed
Draft: "Processing today, will confirm when done..."
### Can Wait (5 emails)
- Team standup notes (no action needed)
- Newsletter: Weekly tech updates
...
### Awareness (12 emails)
- FYI: Team vacation schedule
- Product update announcements
...
### SPAM (8 emails)
- Marketing promotions (auto-filtered)
### Unreplied > 2 Days
- Sarah's question from March 31 (escalate?)
- Vendor quote request from March 30 (follow up?)
### Action Items
1. Send reply to client deadline email
2. Schedule Q2 review meeting
3. Process invoice payment
4. Follow up on unreplied threads

This saves me 30 minutes every morning. I just review the draft replies, tweak them if needed, and send.

Calendar Integration

I also use Claude for calendar awareness:

Calendar management skill
## Calendar Check
Ask Claude Code to:
1. Fetch today's and tomorrow's calendar events
2. Identify conflicts or back-to-back meetings
3. Prep brief summaries for each meeting
4. Remind about prep needed for important meetings
5. Suggest focus time blocks for deep work
Example prompt:
"Check my calendar for today and tomorrow. Tell me what meetings I have,
what prep is needed, and suggest when I can do deep work."

Output:

Calendar check output
## Calendar Overview - April 3-4, 2026
### Today's Meetings (4)
| Time | Meeting | Prep Needed |
|-------------|----------------------|----------------------|
| 9:00-9:30 | Team standup | None |
| 10:00-11:00 | Client sync | Review project status|
| 2:00-3:00 | Q2 planning | Draft 3 key metrics |
| 4:30-5:00 | 1:1 with manager | Update task list |
### Conflicts Found
- Back-to-back: Q2 planning (2-3) and 1:1 (4:30-5)
Suggestion: Move 1:1 to 3:30 or 5:30
### Focus Time Available
- 11:30-1:30 (2 hours) - Good for deep work
- 5:00-6:00 (1 hour) - Wrap-up time
### Tomorrow's Key Events
- 10:00 AM: Product demo (prepare slides)
- 3:00 PM: All-hands meeting (review announcements)

Common Mistakes I Made

Mistake 1: Wrong API Permissions

I initially added only Mail.Read. But when I tried to draft replies, Claude couldn’t send them. I needed Mail.Send and Mail.ReadWrite.

Permission checklist
Start with these:
- Mail.Read (read emails)
- Calendars.Read (view calendar)
Add later if needed:
- Mail.ReadWrite (draft and send)
- Mail.Send (send only, less intrusive)
- Calendars.ReadWrite (create events)
- Tasks.ReadWrite (manage To Do)

Mistake 2: Client Credentials vs Delegated

I used client credentials flow first. This gives app-level access, not user-level. Claude could access any mailbox in the organization - not what I wanted.

The delegated flow (user logs in) restricts access to the authenticated user’s data. This is safer and what most individuals need.

OAuth flow comparison
Client Credentials (app-level):
- Access all users' data in tenant
- Requires admin consent
- No user login prompt
Delegated (user-level):
- Access only authenticated user's data
- User logs in via OAuth
- More appropriate for personal use

Mistake 3: Token Caching

OAuth tokens expire. Initially, my MCP server didn’t cache tokens properly. Every call triggered a new authentication prompt.

The fix: store tokens in a file or environment variable, check expiration, refresh when needed.

Token caching pattern
import json
from pathlib import Path
from datetime import datetime
TOKEN_CACHE = Path.home() / ".claude" / "graph_token.json"
async def get_cached_token():
"""Get token from cache or authenticate"""
if TOKEN_CACHE.exists():
cached = json.loads(TOKEN_CACHE.read_text())
if datetime.fromisoformat(cached["expires"]) > datetime.now():
return cached["token"]
# Token expired or missing - authenticate
token_data = await authenticate()
TOKEN_CACHE.write_text(json.dumps({
"token": token_data["access_token"],
"expires": token_data["expires_on"]
}))
return token_data["access_token"]

Mistake 4: Giving Up Too Early

The setup took me three attempts. First attempt: wrong Azure permissions. Second: OAuth flow confusion. Third: token caching issues. Each failure was frustrating. But each fix was learnable.

The Reddit commenter who “fired my ex wife who was doing my accounts” took similar persistence. They built a complete invoice workflow - but only after multiple iterations.

Testing Your Integration

Before trusting Claude with real email:

Integration test steps
1. Test read operations first
- Fetch 5 recent emails
- Verify subjects and senders match
2. Test calendar read
- Fetch today's events
- Verify times and titles match
3. Draft-only test
- Ask Claude to draft a reply
- Review draft, don't auto-send
- Manually send if satisfied
4. Gradual automation
- Start with read-only
- Add write permissions after confidence
- Keep manual review for important emails

Summary

In this post, I showed how to connect Claude to Microsoft 365 Outlook and Calendar without Claude Teams. The key steps:

  1. Register an Azure app with Microsoft Graph API permissions
  2. Configure Claude Code with an Outlook MCP server (or custom Graph API server)
  3. Authenticate via OAuth to connect your Microsoft account
  4. Create skills for email triage and calendar management

The setup is technical but enables exactly what I needed: daily email categorization, reply drafting, calendar awareness, and task tracking. All from Claude Pro, without the Teams subscription barrier.

For ADHD users like me, this integration transforms inbox chaos into manageable action items. Claude becomes the executive assistant I always needed but couldn’t afford.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments