I Built AI Agents in Microsoft 365—Here's What Changed
n8n just shipped native Microsoft 365 Agent integration — and it's not just another OAuth connector. Your workflow becomes a registered agent with its own identity inside Teams and Outlook. Here's exactly how I deployed client intake and lead qualification agents that appear as real team members.
The Shift That Caught Me Off Guard
I've been building automation workflows in n8n for three years. Most of the time, the pattern is predictable: webhook comes in, logic runs, something gets written to a database or an email goes out. The workflow is invisible. It lives behind the scenes and nobody thinks about it except the person who built it.
That changed when n8n launched native Microsoft Agent 365 integration. For the first time, the workflow isn't just an automation — it's a participant. You register your n8n workflow as a Microsoft 365 Agent, and it shows up in Teams with a profile card, a display name, and the ability to post messages, respond to mentions, and send email from Outlook. It's not a bot in the old sense. It's an agent with identity.
I run VIXI LLC out of Allen, TX — an agency that does AI automation and attribution work for marketing clients. Most of our clients are either deep in Microsoft 365 ecosystems or adjacent to them. When I saw the n8n release notes for the Microsoft Agent 365 node, I immediately thought about the two workflows that cost my team the most time: client intake and lead qualification. Both involve reading incoming communication, making decisions, and writing back to a communication channel. Both are perfect candidates for an agent that lives inside the tools clients already use.
Here's the full setup — what I built, how I built it, and what changed after the agents went live.
What this post covers:
- Azure AD app registration — the correct permissions, the admin consent trap, and how to avoid re-doing it twice
- Client intake agent — email trigger → Claude parsing → Teams post → calendar event
- Lead qualification agent — Teams mention trigger → scoring → auto-response with escalation
- Token refresh and auth edge cases — what breaks in long-running workflows and how to fix it
- What actually changed — real numbers on intake response time and client interaction patterns
What n8n's Microsoft Agent 365 Integration Actually Is
Before getting into setup, it's worth being precise about what this integration is — because the name covers a lot of ground and Microsoft's own documentation conflates several things.
The Microsoft 365 Agents SDK is Microsoft's replacement for the Azure Bot Framework. Where Bot Framework agents were primarily Teams-only and felt bolted on, M365 Agents are first-class participants across the entire Microsoft 365 surface: Teams channels, Teams DMs, Outlook email threads, Copilot Chat, and eventually SharePoint. An M365 Agent has an identity — a registered Azure AD application — not just a webhook URL.
What n8n did is wrap this SDK so that your workflow logic becomes the agent's reasoning layer. Instead of writing a Node.js bot service that handles conversation turn events and manages state, you build an n8n workflow. The trigger node listens for agent events (messages, mentions, email arrivals). Your workflow nodes handle the logic. The output nodes post back into Teams or Outlook. n8n handles the plumbing.
The key distinction from a traditional Teams bot:
| Traditional Teams Bot | M365 Agent via n8n |
|---|---|
| Requires always-on bot service (Azure or self-hosted) | n8n workflow executes on trigger, no persistent service needed |
| Only visible in Teams | Visible in Teams, Outlook, Copilot Chat |
| Profile card is minimal, feels like a bot | Full M365 profile card — name, avatar, job title |
| No native email integration | Can send/receive email from Outlook via Graph API |
The permissions model is standard Microsoft Graph API scopes. You request the scopes your agent needs (Chat.ReadWrite, ChannelMessage.Send, Mail.Send, Calendars.ReadWrite), and a tenant admin grants consent. After that, the agent can act with those permissions across the M365 tenant.
Prerequisites & Azure App Registration
Before touching n8n, you need an Azure AD app registration. This is the identity your agent will use when it talks to Microsoft 365. Here's the exact setup that worked for me — including the mistake I made on the first attempt.
App Registration Steps
Go to portal.azure.com → Azure Active Directory → App registrations → New registration. Name it something descriptive — I used vixi-intake-agent. Set the supported account type to Single tenant unless you're building for multiple orgs. Redirect URI is not required for application permissions (client credentials flow).
After registration, navigate to API permissions → Add a permission → Microsoft Graph → Application permissions and add:
// Required Graph API permissions (Application type, not Delegated) Chat.ReadWrite.All // Read and write Teams chat messages ChannelMessage.Send // Post to Teams channels Mail.Send // Send email from Outlook Calendars.ReadWrite // Create calendar events User.Read.All // Resolve user profiles by email // Optional but useful Files.ReadWrite.All // Attach files in Teams posts TeamsAppInstallation.ReadWriteForChat.All // Manage agent installation
The first mistake I made: I added Delegated permissions instead of Application permissions. Delegated permissions require a signed-in user — which means your n8n workflow would need to impersonate someone. Application permissions let the agent act with its own identity. For an intake agent running 24/7 without human interaction, you need Application permissions.
Admin Consent
Application permissions require tenant admin consent before they activate. In the API permissions panel, click Grant admin consent for [tenant]. If you're not the tenant admin, you'll need to send the admin a consent URL. The consent URL format:
https://login.microsoftonline.com/{tenant_id}/adminconsent
?client_id={your_app_client_id}
&redirect_uri=https://portal.azure.com
# After admin approval, generate a client secret:
# Azure AD → App registrations → {your app} → Certificates & secrets
# → New client secret → Copy the VALUE (not the ID) immediately
# It won't be shown againn8n Credential Setup
In n8n, go to Credentials → Add credential → Microsoft 365 Agent. You need three values from Azure:
// n8n Microsoft 365 Agent credential
{
"tenant_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"client_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"client_secret": "your_client_secret_value_here",
"auth_type": "client_credentials" // application permissions flow
}Building the Client Intake Agent
The client intake agent handles the first touchpoint when a new prospect reaches out. Before this, the process was: email lands in a shared inbox, a human reads it, manually formats a summary, posts to a Teams channel, and creates a Jira ticket. On a busy week, that delay was 2–4 hours. On weekends, it was Monday morning.
The new flow runs in under 90 seconds regardless of when the email arrives.
Workflow Architecture
Node sequence:
- Microsoft 365 Mail Trigger — watches intake@vixi.agency for new messages
- Set — extracts subject, sender, body, timestamp into clean variables
- HTTP Request (Claude Haiku) — structured extraction: name, company, budget, use case, urgency
- Supabase — checks existing client table for duplicate email domain
- IF — routes new leads vs. existing client follow-ups differently
- Microsoft 365 Agent — Post to Channel — posts structured summary to #new-leads Teams channel
- Microsoft 365 Agent — Create Event — adds 15-min intake review to shared Outlook calendar
- Microsoft 365 Agent — Send Email — sends confirmation email to prospect from intake@vixi.agency
The Claude Extraction Prompt
The key to making this reliable is giving Claude Haiku a strict output schema. Freeform summaries are useless for downstream automation — you need structured data you can parse.
// System prompt for Claude Haiku extraction
You are an intake parser for a marketing automation agency.
Extract structured data from the email below.
Return ONLY valid JSON — no commentary, no markdown fences.
Schema:
{
"sender_name": "string | null",
"company": "string | null",
"budget_range": "under_5k | 5k_25k | 25k_100k | 100k_plus | unknown",
"use_case": "string (max 100 chars)",
"urgency": "immediate | this_month | exploring | unknown",
"existing_tech": ["array of mentioned tools/platforms"],
"key_question": "string (the single most important thing they're asking)"
}
Email:
Subject: {{$json.subject}}
From: {{$json.from}}
Body: {{$json.body}}The Teams Post Template
When the agent posts to the #new-leads channel, it uses an Adaptive Card so the summary is scannable at a glance. The agent's profile card shows as the sender — not "n8n Webhook" or an email address. It looks like a team member posted it.
// Adaptive Card payload (simplified)
{
"type": "AdaptiveCard",
"version": "1.5",
"body": [
{
"type": "TextBlock",
"text": "🆕 New Intake: {{sender_name}} @ {{company}}",
"weight": "Bolder",
"size": "Medium"
},
{
"type": "FactSet",
"facts": [
{ "title": "Budget", "value": "{{budget_range}}" },
{ "title": "Use Case", "value": "{{use_case}}" },
{ "title": "Urgency", "value": "{{urgency}}" },
{ "title": "Key Ask", "value": "{{key_question}}" }
]
}
],
"actions": [
{
"type": "Action.OpenUrl",
"title": "View Email",
"url": "{{email_deep_link}}"
}
]
}Lead Qualification Workflow
The second agent handles inbound lead qualification via Teams. Prospects who get added to a shared Teams workspace (common in enterprise sales and partnership contexts) can mention the agent directly or DM it. The agent qualifies them without a human needing to respond first.
Trigger: Teams Mention
The n8n Microsoft Agent 365 trigger node listens for two event types: direct messages to the agent and mentions (@IntakeAgent) in channels the agent is installed in. When either fires, the workflow receives the message content, the sender's Teams ID, and the conversation context.
Scoring Logic
I use Claude Sonnet for qualification — the decision complexity is higher than extraction, so Haiku doesn't handle edge cases as reliably. The scoring function evaluates four dimensions:
// Claude Sonnet qualification prompt
You are a lead qualification agent for a marketing automation agency.
Score this lead on four dimensions, each 0-10.
Return JSON only.
Dimensions:
- budget_fit: Do they have budget for $5k+ engagements?
- use_case_fit: Does their problem match our core services (AI agents, attribution, automation)?
- urgency: Are they in buying mode or just exploring?
- authority: Are they a decision maker or researcher?
Also provide:
- recommended_action: "book_call" | "send_case_study" | "add_to_nurture" | "decline"
- response_message: A 2-3 sentence Teams message to send back (professional, specific to their ask)
- escalate: true/false (true if total score >= 28 or urgency >= 8)
Lead message: {{$json.message_text}}
Sender profile: {{$json.sender_display_name}}, {{$json.sender_job_title}}Auto-Response and Escalation
The agent sends Claude's drafted response_message directly back in the Teams thread. For high-score leads (escalate: true), two additional actions fire in parallel:
// Escalation branch (runs in parallel when escalate === true)
// 1. Slack DM to me (via n8n Slack node)
{
"channel": "D_MY_USER_ID",
"text": "🔥 Hot lead: {{sender_name}} (score: {{total_score}}/40)\nAction: {{recommended_action}}\nContext: {{use_case_fit_reason}}"
}
// 2. Calendly booking link sent via Teams
// Agent sends a follow-up message in the same thread:
"I'd love to connect for a focused 30 minutes.
Here's my calendar: [Calendly link]
I'm typically available within 24 hours."
// 3. Supabase write — lead record
{
"table": "leads",
"data": {
"name": "{{sender_name}}",
"teams_id": "{{sender_teams_id}}",
"score": {{total_score}},
"recommended_action": "{{recommended_action}}",
"source": "teams_agent",
"created_at": "{{$now}}"
}
}The sub-30-score leads still get a response — Claude writes one tailored to their situation — but there's no Slack alert and no calendar push. They go into a Supabase nurture queue that feeds a weekly review.
What Changes When the Agent Is a Team Member
The technical setup is interesting, but the behavioral shift is more significant. Here's what changed after both agents went live.
Response Time: Manual → <90 Seconds
Before the intake agent: median response time to a new intake email was 3.2 hours. That's wall-clock time from email arrival to a human posting context in Teams. After: the agent posts within 90 seconds of email delivery, regardless of time of day. Weekend intakes no longer wait until Monday.
This matters beyond the obvious. Fast response signals professionalism in a way that a 3-hour delay doesn't. Three clients in the first month mentioned that the quick turnaround was one of the reasons they moved forward.
Client Psychology: Named Agent vs. Webhook
There's a meaningful difference between a message that says "From: n8n-webhook@yourdomain.com" and a message that says "From: VIXI Intake Agent" with a profile photo. Clients and team members respond differently. The named agent feels like a team member doing a job — which is exactly what it is. The webhook feels like a notification nobody owns.
I've watched the Teams channel dynamics change. Team members now reply to the agent's message to add context or ask questions. That creates a conversation thread around each intake. Previously, the manual post was just a dump of information with no clear ownership.
Compliance: M365 Audit Trail
Every message the agent sends is logged in the Microsoft 365 compliance center under the agent's identity. This is actually valuable for client engagements where there are data handling requirements. Instead of trying to recreate "what did the automation do with that email," there's a native audit trail in M365 that the client's IT team can access directly.
Real Numbers (First 30 Days)
| Metric | Before | After |
|---|---|---|
| Median intake response time | 3.2 hours | 87 seconds |
| Intakes requiring manual triage | 100% | 18% (edge cases) |
| Leads scored before first human touch | 0% | 94% |
| n8n execution cost (Claude Haiku) | — | ~$0.003/intake |
The Authorization Edge Cases Nobody Mentions
Most tutorials for Microsoft Graph API integrations stop after "it works." Here are the edge cases that will break you in production if you don't plan for them.
Token Expiry in Long-Running Workflows
Client credentials tokens expire after 3600 seconds (1 hour). For workflows triggered by email or Teams events that fire frequently, n8n re-authenticates automatically on each execution — no issue. For workflows with long polling intervals or that process large batches, you can hit a token expiry mid-execution.
The fix: use the HTTP Request node to explicitly request a fresh token at the start of any workflow that might run for more than 50 minutes. Don't rely on n8n's credential caching for long-running jobs.
// Force fresh token via HTTP Request node
POST https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id={{$env.AZURE_CLIENT_ID}}
&client_secret={{$env.AZURE_CLIENT_SECRET}}
&scope=https://graph.microsoft.com/.default
// Use {{$json.access_token}} in subsequent nodesGraph API Rate Limits
Microsoft Graph throttles at the tenant level, not per-app. If your tenant has other apps also hitting Graph (common in larger orgs), you can hit 429 errors even if your individual app is within its limits. n8n doesn't automatically retry on 429.
Add an Error Trigger workflow that catches 429 responses and re-queues the failed execution with a 60-second delay. For intake agents processing less than 50 emails/day, you won't hit this — but it's worth building the retry logic before you need it.
Single-Tenant vs. Multi-Tenant App Registration
If you're building for your own org only, use single-tenant — simpler consent flow, no publisher verification requirements. If you want to deploy the same agent for multiple clients, you need a multi-tenant registration and publisher verification (which requires a Microsoft Partner Network ID or a domain verification step that can take days).
My recommendation: start single-tenant per client. When you have 5+ clients on the same agent design, evaluate whether the multi-tenant overhead is worth it versus maintaining separate app registrations.
What I'd Do Differently
Honest retrospective after the first 30 days in production:
Test admin consent in a dev tenant first. Getting admin consent revoked mid-project because of a permissions misconfiguration is not a fun conversation. Spin up a Microsoft 365 Developer tenant (free through the Microsoft 365 Developer Program), do your full app registration and consent flow there, and validate everything before touching a client tenant.
Use environment variables for all Azure credentials. I initially hardcoded tenant ID and client ID in workflow nodes during testing. When I moved to production n8n, I had to hunt down and replace every instance. Store everything as n8n environment variables from day one — AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET.
Version control your workflow JSON. n8n lets you export workflow definitions as JSON. After every significant change, export and commit to git. M365 Agent workflows are complex enough that you absolutely need rollback capability. I lost two hours of configuration work when I accidentally broke a working workflow and couldn't reconstruct the correct node connections.
Don't skip the M365 Agent manifest validation. n8n generates the Teams app manifest when you set up the agent, but it's worth downloading it and running it through the Teams Developer Portal validator. Invalid manifests fail silently in some environments — the agent appears to install but events don't fire.
Build the Supabase logging from day one. Every intake and every qualification decision should write a row to Supabase. You'll want this data for refining the scoring prompts, calculating actual ROI, and debugging edge cases. I added logging as an afterthought at week two and had gaps in my first 30-day data as a result.
Agents That Live Where Your Clients Already Are
The thing that surprised me most about this setup wasn't the technical capability — it was how quickly team members and clients stopped thinking about the agent as automation. Two weeks in, nobody says "the bot did this." They say "Intake posted it to Teams" or "the qualification agent flagged that lead." The agent has a role. It has a name. It does a job.
That shift in language reflects something real about how the work actually flows now. The agent is part of the team in a way that a webhook URL never was. It participates in the same communication channels, creates artifacts in the same calendars, and leaves a trail in the same compliance logs. It's not a separate system that feeds data into M365 — it's inside M365.
For agencies and teams that live in Microsoft 365, this integration is the most natural AI automation deployment I've built. No new tools to learn. No separate dashboard to check. The agents are just there, in Teams and Outlook, doing work.
If you're running client communications or lead operations in a Microsoft 365 environment, this is worth the Azure setup overhead. The 90-second intake response alone paid for the build time in the first week.
Building AI agents for your Microsoft 365 stack?
I'm taking on a limited number of agency automation projects this quarter. If you're running on M365 and want intake, qualification, or reporting agents deployed for your team, reach out.
Get in touch →