🤖AI Agents Guide
TutorialsComparisonsReviewsExamplesIntegrationsUse CasesTemplatesGlossary
Get Started
🤖AI Agents Guide

Your comprehensive resource for understanding, building, and implementing AI Agents.

Learn

  • Tutorials
  • Glossary
  • Use Cases
  • Examples

Compare

  • Tool Comparisons
  • Reviews
  • Integrations
  • Templates

Company

  • About
  • Contact
  • Privacy Policy

© 2026 AI Agents Guide. All rights reserved.

Home/Integrations/AI Agents + Microsoft Outlook: Setup Guide
IntegrationMicrosoft Outlookintermediate10 min readSetup: 25-35 minutes

AI Agents + Microsoft Outlook: Setup Guide

Step-by-step guide to connecting AI agents with Microsoft Outlook. Learn how to automate email triage, calendar scheduling, contact management, and Teams meeting creation using LangChain, n8n, and the Microsoft Graph API.

Email interface on laptop representing Microsoft Outlook AI agent automation
Photo by Markus Spiske on Unsplash
By AI Agents Guide Team•February 28, 2026

Table of Contents

  1. What AI Agents Can Do With Outlook Access
  2. Setting Up Microsoft Graph API Access
  3. Register an Azure App
  4. Option 1: No-Code with n8n
  5. Daily Email Digest Workflow
  6. Option 2: LangChain with Python
  7. Build Microsoft Graph / Outlook Tools
  8. Outlook AI Assistant Agent
  9. Rate Limits and Best Practices
  10. Next Steps
Office workspace with email management representing Outlook calendar automation
Photo by Solen Feyissa on Unsplash

Microsoft Outlook, powered by Microsoft Graph, gives AI agents access to one of the most complete enterprise communication ecosystems available — email, calendar, contacts, and Teams in a single API. For organizations running on Microsoft 365, Outlook AI integration is often the highest-leverage automation available because it connects to the systems employees already use throughout their workday.

This guide covers building an Outlook email and calendar agent using Microsoft Graph API and LangChain.

What AI Agents Can Do With Outlook Access#

Email Management

  • Triage incoming email by urgency and category, applying folders and flags automatically
  • Draft contextually appropriate replies using thread history and previous email patterns
  • Extract action items and deadlines from emails and sync them to Planner or Tasks
  • Generate executive summaries of daily email volume for quick scanning

Calendar Automation

  • Schedule meetings with Teams links based on mutual availability
  • Reschedule conflicting events and notify attendees automatically
  • Block focus time to protect deep work from meeting creep
  • Generate pre-meeting briefs from email threads and attachments

Cross-Platform Workflows

  • Route high-priority emails to Teams channels for team visibility
  • Sync Outlook tasks with SharePoint project lists
  • Trigger workflows based on email content (invoice received → start approval flow)

Setting Up Microsoft Graph API Access#

Register an Azure App#

  1. Go to Azure Portal ↗ → Azure Active Directory → App registrations
  2. Click New registration — name your app, set redirect URI to http://localhost
  3. Note your Application (client) ID and Directory (tenant) ID
  4. Go to Certificates & secrets → New client secret → copy the value
  5. Go to API permissions → Add permission → Microsoft Graph → Delegated:
    • Mail.ReadWrite, Calendars.ReadWrite, User.Read
  6. Click Grant admin consent
pip install msal requests langchain langchain-openai python-dotenv
import os
import msal
import requests
from dotenv import load_dotenv

load_dotenv()

CLIENT_ID = os.getenv("AZURE_CLIENT_ID")
CLIENT_SECRET = os.getenv("AZURE_CLIENT_SECRET")
TENANT_ID = os.getenv("AZURE_TENANT_ID")
AUTHORITY = f"https://login.microsoftonline.com/{TENANT_ID}"
SCOPES = ["https://graph.microsoft.com/Mail.ReadWrite",
          "https://graph.microsoft.com/Calendars.ReadWrite",
          "https://graph.microsoft.com/User.Read"]
GRAPH_ENDPOINT = "https://graph.microsoft.com/v1.0"


def get_access_token():
    """Get Microsoft Graph access token using device code flow."""
    app = msal.PublicClientApplication(CLIENT_ID, authority=AUTHORITY)
    # Try cache first
    accounts = app.get_accounts()
    if accounts:
        result = app.acquire_token_silent(SCOPES, account=accounts[0])
        if result and "access_token" in result:
            return result["access_token"]
    # Device code flow for first run
    flow = app.initiate_device_flow(scopes=SCOPES)
    print(flow.get("message", ""))  # Shows login URL and code
    result = app.acquire_token_by_device_flow(flow)
    return result.get("access_token")


def graph_get(endpoint: str, params: dict = None) -> dict:
    token = get_access_token()
    headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
    response = requests.get(f"{GRAPH_ENDPOINT}/{endpoint}", headers=headers, params=params)
    response.raise_for_status()
    return response.json()


def graph_post(endpoint: str, data: dict) -> dict:
    token = get_access_token()
    headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
    response = requests.post(f"{GRAPH_ENDPOINT}/{endpoint}", headers=headers, json=data)
    response.raise_for_status()
    return response.json()

Option 1: No-Code with n8n#

Daily Email Digest Workflow#

  1. Schedule Trigger: Run at 7am weekdays
  2. Microsoft Outlook node (n8n built-in): Fetch emails received since yesterday
  3. Code node: Filter unread emails, group by sender domain
  4. OpenAI: "Summarize these emails into a 5-bullet daily digest. Flag any requiring urgent response."
  5. Microsoft Teams node: Post digest to personal chat or team channel

n8n's Microsoft Outlook node handles OAuth token refresh automatically using the configured credential.


Option 2: LangChain with Python#

Build Microsoft Graph / Outlook Tools#

from langchain.tools import tool
from datetime import datetime, timedelta, timezone


@tool
def list_unread_emails(folder: str = "inbox", max_results: int = 10) -> str:
    """List unread emails from Outlook. folder: 'inbox', 'junkemail', 'drafts'."""
    messages = graph_get(
        f"me/mailFolders/{folder}/messages",
        params={
            "$filter": "isRead eq false",
            "$select": "id,subject,from,receivedDateTime,bodyPreview",
            "$top": max_results,
            "$orderby": "receivedDateTime desc"
        }
    )
    items = messages.get("value", [])
    if not items:
        return "No unread emails found"

    result = [f"Unread emails ({len(items)} shown):"]
    for msg in items:
        sender = msg.get("from", {}).get("emailAddress", {})
        result.append(f"\nID: {msg['id'][:20]}...\nFrom: {sender.get('name', 'Unknown')} <{sender.get('address', '')}>\nSubject: {msg.get('subject', 'No subject')}\nPreview: {msg.get('bodyPreview', '')[:200]}")
    return "\n".join(result)


@tool
def get_email_body(message_id: str) -> str:
    """Get the full body of an Outlook email by message ID."""
    msg = graph_get(
        f"me/messages/{message_id}",
        params={"$select": "subject,from,body,receivedDateTime,toRecipients"}
    )
    sender = msg.get("from", {}).get("emailAddress", {})
    body_content = msg.get("body", {}).get("content", "")
    # Strip HTML tags for plain text
    import re
    clean_body = re.sub(r'<[^>]+>', '', body_content)[:3000]
    return f"From: {sender.get('name')} <{sender.get('address')}>\nSubject: {msg.get('subject')}\nDate: {msg.get('receivedDateTime')}\n\n{clean_body}"


@tool
def create_draft_reply(message_id: str, body_text: str) -> str:
    """Create a draft reply to an Outlook email. Draft appears in Drafts folder for review."""
    draft = graph_post(f"me/messages/{message_id}/createReply", {})
    draft_id = draft.get("id")

    # Update the draft body
    token = get_access_token()
    headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
    requests.patch(
        f"{GRAPH_ENDPOINT}/me/messages/{draft_id}",
        headers=headers,
        json={"body": {"contentType": "Text", "content": body_text}}
    )
    return f"Draft reply created (ID: {draft_id[:20]}...) — review in Outlook Drafts before sending"


@tool
def create_calendar_event(subject: str, start_datetime: str, duration_minutes: int = 30,
                          attendee_emails: list = None, body: str = "",
                          add_teams_link: bool = True) -> str:
    """
    Create a calendar event in Outlook with optional Teams link.
    start_datetime format: 'YYYY-MM-DDTHH:MM:SS' (local time).
    """
    start = datetime.fromisoformat(start_datetime)
    end = start + timedelta(minutes=duration_minutes)

    event_body = {
        "subject": subject,
        "body": {"contentType": "Text", "content": body},
        "start": {"dateTime": start.isoformat(), "timeZone": "America/New_York"},
        "end": {"dateTime": end.isoformat(), "timeZone": "America/New_York"},
    }

    if attendee_emails:
        event_body["attendees"] = [
            {"emailAddress": {"address": email}, "type": "required"}
            for email in attendee_emails
        ]

    if add_teams_link:
        event_body["isOnlineMeeting"] = True
        event_body["onlineMeetingProvider"] = "teamsForBusiness"

    event = graph_post("me/events", event_body)
    join_url = event.get("onlineMeeting", {}).get("joinUrl", "No Teams link")
    return f"Event created: '{subject}'\nTime: {start.strftime('%B %d at %I:%M %p')}\nTeams: {join_url}"


@tool
def get_todays_calendar() -> str:
    """Get today's Outlook calendar events."""
    today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
    tomorrow = today + timedelta(days=1)

    events = graph_get("me/calendarView", params={
        "startDateTime": today.isoformat() + "Z",
        "endDateTime": tomorrow.isoformat() + "Z",
        "$select": "subject,start,end,attendees,isOnlineMeeting",
        "$orderby": "start/dateTime"
    })
    items = events.get("value", [])
    if not items:
        return "No events today"

    lines = [f"Today's calendar ({today.strftime('%A, %B %d')}):"]
    for event in items:
        start_str = event.get("start", {}).get("dateTime", "")
        start_time = datetime.fromisoformat(start_str[:19]).strftime("%I:%M %p") if start_str else "TBD"
        attendee_count = len(event.get("attendees", []))
        online = "Teams" if event.get("isOnlineMeeting") else "In-person"
        lines.append(f"  {start_time}: {event.get('subject', 'No title')} | {attendee_count} attendees | {online}")
    return "\n".join(lines)

Outlook AI Assistant Agent#

from langchain_openai import ChatOpenAI
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate

llm = ChatOpenAI(model="gpt-4o", temperature=0.2)
tools = [list_unread_emails, get_email_body, create_draft_reply,
         create_calendar_event, get_todays_calendar]

prompt = ChatPromptTemplate.from_messages([
    ("system", """You are a Microsoft 365 AI assistant with access to Outlook email and calendar.

Your core behaviors:
- Always create drafts, never send emails autonomously
- Check calendar context before suggesting meeting times
- Extract action items from emails into clear bullet points
- Summarize email threads concisely before drafting replies

Communication style: professional, concise, action-oriented."""),
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}"),
])

agent = create_tool_calling_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True, max_iterations=8)

Rate Limits and Best Practices#

Microsoft Graph limitValue
Per user throttle10,000 req/10 min
Batch request sizeMax 20 requests
Throttle response429 with Retry-After header

Best practices:

  • Use batch requests: Combine up to 20 API calls in one POST /$batch request for bulk processing
  • Cache the access token: MSAL handles this, but ensure you reuse tokens until they expire rather than acquiring new ones per call
  • Use delta queries: For agents monitoring mailbox changes, use /me/messages/delta instead of polling the full message list
  • Scope minimally: Request only the Graph permissions your agent actually uses to reduce security exposure

Next Steps#

  • AI Agents Gmail Integration — Gmail-based email automation patterns
  • AI Agents Google Calendar Integration — Google Calendar scheduling automation
  • AI Agents Slack Integration — Bridge Outlook and Slack workflows
  • Build an AI Agent with LangChain — Complete framework tutorial

Related Integrations

How to Integrate AI Agents with Airtable

Step-by-step guide to connecting AI agents with Airtable. Learn how to automate record creation, data enrichment, workflow triggers, and database management using LangChain, n8n, and the Airtable REST API.

How to Integrate AI Agents with Asana

Step-by-step guide to connecting AI agents with Asana. Learn how to automate task creation, project updates, workload analysis, and deadline tracking using LangChain, n8n, and the Asana REST API.

AI Agents + Google BigQuery: Setup Guide

Step-by-step guide to connecting AI agents with Google BigQuery. Learn how to automate SQL queries, build analytics pipelines, detect anomalies, and generate business reports using LangChain, n8n, and the BigQuery Python SDK.

← Back to All Integrations