OpenClawSkills
GitHub
Channels β€’ TutorialHeader.readTime

Microsoft Teams

Microsoft Teams bot: support status, capabilities, and configuration (Bot Framework + RSC + optional Graph).

Tutorial.alert.info

Note: "Abandon all hope, ye who enter here."

Update: 2026-01-21

Status: Supports text and DM attachments; channel/group file sending requires sharePointSiteId + Graph permissions. Polls are sent via Adaptive Cards.

Tutorial.step

Plugin Installation Required

Microsoft Teams is provided as a plugin and is not bundled with core installation.

Breaking change (2026.1.15): Teams has been moved out of core. Using Teams requires installing the plugin (this keeps core lighter and allows Teams dependencies to be updated independently).

Install via CLI (npm registry):

Bash
openclaw plugins install @openclaw/msteams

Local installation (when running from a git repository):

Bash
openclaw plugins install ./extensions/msteams

If you selected Teams in configure/onboarding and a git checkout is detected, OpenClaw will automatically provide the local installation path.

Details: ''/plugin''

Tutorial.step

Quick Setup for Beginners

1. Install the Microsoft Teams plugin.

2. Create an Azure Bot (App ID + client secret + tenant ID).

3. Write credentials to OpenClaw configuration.

4. Expose /api/messages via public URL or tunnel (default port 3978).

5. Install/upload Teams app package and start gateway.

Minimal configuration:

Json5
{
  channels: {
    msteams: {
      enabled: true,
      appId: "<APP_ID>",
      appPassword: "<APP_PASSWORD>",
      tenantId: "<TENANT_ID>",
      webhook: { port: 3978, path: "/api/messages" },
    },
  },
}

Note: Group chats are blocked by default (channels.msteams.groupPolicy: "allowlist"). To allow group/channel replies, set channels.msteams.groupAllowFrom (or use groupPolicy: "open" to allow any member, but still require mention by default).

Tutorial.step

Goals

- Chat with OpenClaw in Teams DMs, group chats, or channels.

- Maintain deterministic routing: replies always go back to the source conversation.

- Default security: groups/channels require mention by default (unless configured otherwise).

Tutorial.step

Config Writes

By default, Teams is allowed to write configuration updates triggered by /config set|unset back to the configuration file (requires commands.config: true).

Disable:

Json5
{
  channels: { msteams: { configWrites: false } },
}
Tutorial.step

Access Control (DMs + Groups/Channels)

Direct Messages (DMs)

- Default: channels.msteams.dmPolicy = "pairing". Unknown senders are ignored until approved.

- channels.msteams.allowFrom supports AAD object IDs, UPNs (email style), or display names. The wizard resolves names to IDs when Graph is available.

Groups/Channels

- Default: channels.msteams.groupPolicy = "allowlist" (blocks unless you add groupAllowFrom).

- channels.msteams.groupAllowFrom controls who can trigger in group chats/channels (falls back to channels.msteams.allowFrom when not set).

- groupPolicy: "open" allows any member (still requires mention by default).

- To completely disable channel/group chat: channels.msteams.groupPolicy: "disabled".

Example:

Json5
{
  channels: {
    msteams: {
      groupPolicy: "allowlist",
      groupAllowFrom: ["[email protected]"],
    },
  },
}

ChannelsMsteamsPage step 05: P10

Tutorial.step

Team/Channel Allowlist (Optional)

You can list allowed teams and channels in channels.msteams.teams:

- Team key can be team ID or name

- Channel key can be conversation ID or name

- When groupPolicy="allowlist" and teams allowlist exists, only listed teams/channels are accepted (still requires mention by default)

- Wizard accepts Team/Channel input and writes to config for you

- At startup, attempts to resolve team/channel and user allowlist names to IDs and logs (requires corresponding Graph permissions)

Example:

Json5
{
  channels: {
    msteams: {
      groupPolicy: "allowlist",
      teams: {
        "My Team": {
          channels: {
            General: { requireMention: true },
          },
        },
      },
    },
  },
}
Tutorial.step

How It Works (Brief)

1. Install Teams plugin.

2. Create Azure Bot (App ID + secret + tenant ID).

3. Build Teams app package (manifest.zip) referencing your bot and including necessary RSC permissions (see below).

4. Upload/install app to target team (or personal scope for DMs).

5. Configure ~/.openclaw/openclaw.json (or env vars) and start gateway.

6. Gateway listens on Bot Framework webhook (default POST /api/messages).

Tutorial.step

Azure Bot Setup (Core Step)

#

Tutorial.step

1) Create Azure Bot

1. Open: ''Create Azure Bot''

2. Fill in <strong>Basics</strong> (example):

| Field | Value |

| ------------------- | ------------------------------------------------------------- |

| Posts (classic) | Card-style main post + threaded replies below | thread (default) |

| Threads (Slack-like)| Linear message flow, more like Slack | top-level |

If configured incorrectly:

- Using <code>thread</code> in linear Threads channels: replies will nest weirdly

- Using <code>top-level</code> in classic Posts channels: replies become new top-level posts, not under thread

Override per channel:

Json
{
  "msteams": {
    "replyStyle": "thread",
    "teams": {
      "19:[email protected]": {
        "channels": {
          "19:[email protected]": { "replyStyle": "top-level" }
        }
      }
    }
  }
}
Tutorial.step

Attachments and Images

Current limitations:

- Direct Messages (DMs): Images and file attachments available (Teams bot file APIs).

- Channels/Group Chats: Attachments are stored in M365 (SharePoint/OneDrive). Webhook payload only contains HTML stub, not actual file bytes. To download channel attachments, Graph API permissions must be enabled.

Without Graph permissions, images in channels will enter context as plain text (bot cannot see image content).

By default, OpenClaw only downloads media from Microsoft/Teams hostnames. Override with channels.msteams.mediaAllowHosts (["*"] allows any host).

Tutorial.step

Sending Files in Groups/Channels

Bot can send files in DMs using FileConsentCard (built-in flow). But sending files in group chats/channels requires additional configuration:

| Scenario | Send method | Required settings |

| ---------------------------- | -------------------------------------------- | ----------------------------------------- |

| User (by ID) | user:<aad-object-id> | user:40a1a0ed-4ff2-4164-a219-55518990c197 |

| User (by name) | user:<display-name> | user:John Smith (requires Graph) |

| Group/Channel | conversation:<conversation-id> | conversation:19:[email protected] |

| Group/Channel (raw) | <conversation-id> | 19:[email protected] (when contains @thread) |

Without user: prefix, names default to resolving as group/team. Always use user: when messaging people.

Tutorial.step

Proactive Messaging

- Proactive messaging is only possible after user interacts with bot (we save conversation references at that time).

- dmPolicy and allowlists apply to proactive messages as well (see /gateway/configuration).

Tutorial.step

Team/Channel IDs (Common Pitfalls)

The groupId query parameter in Teams URLs is not the team ID used in configuration. Extract from URL path and URL-decode:

Team URL:

Terminal
https://teams.microsoft.com/l/team/19%3ABk4j...%40thread.tacv2/conversations?groupId=...
                                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                    Team ID (URL-decode)

Channel URL:

Terminal
https://teams.microsoft.com/l/channel/19%3A15bc...%40thread.tacv2/ChannelName?groupId=...
                                      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                      Channel ID (URL-decode)

When configuring:

- Team ID = path segment after /team/ (URL-decoded)

- Channel ID = path segment after /channel/ (URL-decoded)

- Ignore <code>groupId</code> query parameter

Tutorial.step

Private Channels

Bot support on private channels is limited:

| Feature | Standard Channel | Private Channel |

| ----------------------------- | --------------- | ---------------------------- |

| Install bot | Yes | Limited |

| Real-time messages (webhook) | Yes | May not be available |

| RSC permissions | Yes | Behavior may differ |

| @mentions | Yes | Available if bot can access |

| Graph history queries | Yes | Yes (with permissions) |

If private channels don't work:

1. Let bot interactions happen in standard channels

2. Use DMs (users can always DM bot)

3. Use Graph to get history (requires ChannelMessage.Read.All)

Tutorial.step

Troubleshooting

Common issues:

- Can't see images in channels: Missing Graph permissions or admin consent. Reinstall Teams app and fully quit/restart Teams.

- No reply in channels: Default requires mention; set <code>channels.msteams.requireMention=false</code> or configure per team/channel.

- Version not updating (Teams still shows old manifest): Remove and add app again, and fully quit Teams to clear cache.

- Webhook test returns 401: Manual curl test without Azure JWT is normal, means endpoint is reachable but auth failed. Use Azure Web Chat for correct testing.

Manifest upload errors:

- "Icon file cannot be empty": Icon file referenced in manifest is 0 bytes. Create valid PNG (<code>outline.png</code> 32Γ—32, <code>color.png</code> 192Γ—192).

- "webApplicationInfo.Id already in use": App is still installed in other team/chat. Uninstall first, or wait 5–10 minutes for propagation.

- "Something went wrong" during upload: Try uploading via https://admin.teams.microsoft.com and open DevTools (F12) β†’ Network to see actual error response.

- Sideloading fails: Try "Upload an app to your org's app catalog" instead of "Upload a custom app".

RSC permissions not working:

1. Confirm <code>webApplicationInfo.id</code> matches bot App ID exactly

2. Re-upload app and reinstall in team/chat

3. Check if organization policy blocks RSC

4. Confirm scope: team uses ChannelMessage.Read.Group; group chat uses ChatMessage.Read.Chat

Tutorial.step

References