Skip to main content
Connect your Comis agent to Microsoft Teams — 1:1 chats, group chats, and team channels, with threaded replies, Adaptive Cards, inbound reactions, and edit/delete. Teams is the one channel that must receive messages on a public HTTPS endpoint: the Bot Framework Connector service pushes every activity to a URL you register, so your Comis gateway has to be reachable from the internet. This page walks you through registering the bot, exposing the endpoint safely, and configuring the three authentication modes.
You don’t need to understand the technical details to use this feature. The configuration examples below are copy-paste ready.

Prerequisites

  • A Microsoft Entra ID (Azure AD) tenant where you can register an application
  • Permission to sideload (upload) a custom app in your Teams tenant, or a Teams administrator who can do it for you
  • A public HTTPS URL that reaches your Comis gateway (see Public endpoint below)

Public endpoint

Microsoft Teams delivers messages by pushing them to your bot. There is no socket, long-poll, or pull option — the Bot Framework Connector service sends each activity as an authenticated HTTPS POST to the messaging endpoint you register. Your Comis gateway must therefore be reachable from the internet at https://your-host/channels/msteams/api/messages.
The messaging endpoint rides your gateway’s existing host, port, and TLS surface (gateway.host / gateway.port) — no separate port is opened. The route is mounted only when channels.msteams.enabled is true; while the channel is disabled (the default) no inbound route exists at all. Every inbound request is validated against the Bot Framework signing keys before it reaches your agent, so an unauthenticated POST to the endpoint is rejected regardless of how you expose it. You have several ways to make the gateway publicly reachable. They are listed most-hardened first; all of them see the same inbound token validation, so the choice is about network exposure, not authentication.
1

Reverse proxy with HTTPS (production)

Terminate TLS at a reverse proxy (Nginx, Caddy) in front of the gateway and forward /channels/msteams/api/messages to it. This gives you full control over certificates, headers, and rate limiting. See Reverse proxy for a worked Nginx and Caddy configuration.
2

Tailscale Funnel (production, no open port)

Tailscale Funnel publishes a local port to the public internet over HTTPS with an auto-provisioned certificate and a stable *.ts.net hostname — without forwarding a port on your router or firewall.Enable Funnel for the node in your tailnet’s ACLs, then expose the gateway port:
Your messaging endpoint becomes https://your-machine.your-tailnet.ts.net/channels/msteams/api/messages. Funnel handles the certificate; Comis still validates every inbound request.
3

Dev tunnel (testing only)

For local testing, a tunnel such as ngrok or Azure Dev Tunnels gives you a temporary public HTTPS URL that forwards to your gateway port. Use the tunnel URL as the messaging endpoint while you iterate; move to a reverse proxy or Funnel before you run in production.

Setup

Teams needs an application registration (which supplies the bot’s app ID and credentials) and a Teams app package (the manifest that sideloads the bot into Teams). You can do this from the command line with the Teams Toolkit, or by hand in the Azure portal.
1

Register the bot and set the messaging endpoint

The Teams Toolkit CLI provisions the app registration, the bot, and a starter manifest without clicking through the Azure portal.
  1. Sign in to your tenant with the CLI
  2. Provision a bot registration — the CLI creates the Entra application and returns its application (client) ID and a generated client secret
  3. Set the bot’s messaging endpoint to https://your-host/channels/msteams/api/messages
  4. Note the directory (tenant) ID of the tenant you signed in to
Keep the client ID, client secret, and tenant ID — they map to appId, appPassword, and tenantId in your Comis config.
2

Build and sideload the Teams app manifest

A bot registration is not visible in Teams until a Teams app package points at it. The package is a zip containing manifest.json (referencing your appId as the bot ID) plus a color and outline icon.Upload the package to Teams — either Upload a custom app from the Teams client, or through the Teams admin center for tenant-wide availability. Once installed, add the bot to a chat, group, or team channel.
3

Grant channel read access (RSC)

To let the bot read channel messages without being @mentioned every time, declare Resource-Specific Consent (RSC) permissions in the manifest (for example ChannelMessage.Read.Group). A team owner consents once when the app is added to the team; the bot then receives channel activities directly. Skip this if you only need 1:1 and group chats or are happy to require an @mention in channels.
4

Configure Comis

Add the Teams channel to your Comis configuration file (~/.comis/config.yaml):
Set the secret in your ~/.comis/.env file:
Never store API keys, tokens, or passwords directly in config.yaml. Use the .env file or Secret Manager for credential management.
5

Restart and verify

Restart the Comis daemon to pick up the new configuration:
Check that the Teams probes pass:
Then message the bot from Teams (a 1:1 chat is the quickest check) and confirm it replies.

Authentication

Teams supports three credential modes, selected with authMode. All three identify the same single-tenant bot application (appId + tenantId); they differ only in how the bot proves its identity when it calls back out to send, edit, delete, and fetch media.
A client secret. Simplest to set up; the secret must be rotated before it expires.
Only appPassword is a secret — keep it in ~/.comis/.env as MSTEAMS_APP_PASSWORD (or a SecretRef). appId, tenantId, certPath, and managedIdentityClientId are plain configuration values.

Configuration

The full option table — all fields, types, and defaults — lives in the Configuration reference. The environment variable is documented in Environment variables. This section covers the one option that carries a security consequence worth spelling out.

Sender allowlist and the conversation-scoped caveat

allowFrom restricts who can talk to the bot. When it is non-empty (and allowMode is the default allowlist), only listed senders reach the agent. Each entry is either an aadObjectId (one specific user) or a conversation.id (a whole conversation).
Allowlisting a conversation id grants access to every current and future member of that conversation — including the right to click Approve on the bot’s approval cards. Approval authority is derived from the verified sender of the click, so a conversation-scoped entry effectively hands approval rights to the whole room. Prefer per-user aadObjectId entries where you need tight control, and scope any conversation-id entry deliberately.

Media

Inbound attachments (images, voice notes, video, documents) are downloaded through a DNS-pinned, SSRF-guarded fetcher. The bot’s token is attached only for recognized Bot Framework Connector attachment hosts and is dropped on any cross-host redirect; the initial host must be a known Teams, SharePoint, or Graph attachment host, and the downloaded bytes are MIME-sniffed before the media pipeline (transcription, vision, document extraction) sees them. Outbound, the agent sends inline images (delivered as a base64 data URI that Teams renders in place). A non-image attachment is delivered by reference as a plain text link rather than inlined — Bot Framework only renders inline data URIs for images, and inlining a multi-megabyte file would blow past that limit.
Uploading files and videos into Teams (the FileConsent / SharePoint flow) is a later addition; today the agent sends inline images and text links.

Liveness

A webhook channel is exempt from the health monitor’s stale-reap: because Teams never opens an outbound connection Comis can watch, a mis-wired or silently broken public endpoint would otherwise report healthy forever, and the only symptom would be a bot that quietly stops receiving messages. Two guards close that gap:
  • comis doctor runs four Teams probes — credentials parse and resolve, the messaging endpoint is reachable, an inbound activity has arrived recently, and a tenant ID is configured. The endpoint and recent-inbound probes skip (rather than fail) when the daemon or gateway is down.
  • A dedicated missed-inbound alert compares the time since the last inbound activity to missedInboundThresholdMs (default 6 hours). On breach it emits a channel:inbound_silent event and a WARN that surfaces as a comis fleet health signal. Lower the threshold for a busy bot where a few hours of silence is itself a red flag.

Live-smoke checklist

The build gates cover the adapter, mapper, renderer, auth, and gateway route. The end-to-end sign-off needs a real Azure Bot registration and a Teams tenant, so run this operator checklist once against a live account:
  • Text round-trips in a 1:1 chat, a group chat, and a team channel
  • An inbound reaction registers; the bot can edit and delete its own messages; a proactive (bot-initiated) message from a cron job or heartbeat delivers
  • An Adaptive Card renders, and clicking Approve on an approval card is authorized to the clicking user — a non-allowlisted user cannot approve
  • An inbound image, voice note, and document each resolve; an outbound inline image renders
  • certificate and managedIdentity auth modes each start cleanly; comis doctor shows the Teams probes green; the missed-inbound alert fires after the configured silence window

Local emulator (self-drive testing)

The live-smoke checklist above needs a real Azure Bot registration. For an offline, no-Azure round trip — and for the self-driving live-test rig — an in-tree emulator plays the Bot Framework side (a fake Connector + AAD token endpoint + a JWKS signer). It lives at test/live/emulators/msteams/ and is driven from test/live/self-driving/. Teams is the inverse of the poll-based channels (Telegram/Signal): inbound is a signed webhook the daemon exposes, and outbound goes to the Connector. So two loopback bridges are needed, and both are off-by-default environment variables set only on the test daemon — never in production:
With both variables unset, the daemon behaves identically to a normal Teams deployment. Setting them on a production daemon would point inbound trust and outbound delivery at a local process — they are strictly for the live-test rig.
Wiring (as the operator, on the test box):
The whole wire stack is also proven offline (no daemon) by the round-trip scenario at test/live/scenarios/channels/msteams-emulator.test.ts, which pushes a signed activity through the real ingress + adapter + connector into the emulator’s oracle.

What your agent can do

Once connected to Teams, your agent can:
  • Send, edit, and delete messages in 1:1 chats, group chats, and team channels
  • Reply in a channel or group thread (threaded on the original activity)
  • Receive reactions (like, heart, laugh, surprised, sad, angry) as inbound signals
  • Send inline images
  • Show a typing indicator while it works
  • Send Adaptive Cards with interactive buttons, including default-deny approval cards for privileged actions
  • Deliver proactive (bot-initiated) messages from cron jobs and heartbeats
  • Mention users with @mentions and detect when it is mentioned
Teams does not support the agent sending reactions (Teams exposes no bot-reaction API, so reactions are inbound only), fetching message history, uploading files or videos (FileConsent / SharePoint is a later addition), native token-by-token DM streaming, or voice/TTS replies.

Platform limits

Troubleshooting

What happened: The public messaging endpoint is not reachable, the route is not mounted, or the channel is disabled.How to fix it: Confirm channels.msteams.enabled is true (the route is mounted only when enabled). Verify the messaging endpoint on the Azure Bot matches https://your-host/channels/msteams/api/messages exactly, and that your reverse proxy, Funnel, or tunnel is forwarding to the gateway port. Run comis doctor — the endpoint and recent-inbound probes tell you which half is broken.
What happened: Bot Framework token validation failed — usually an appId / tenantId mismatch or the wrong single-tenant directory.How to fix it: Check that appId matches the application (client) ID of the registered bot and that tenantId is the directory (tenant) ID the app is registered in. For secret mode, confirm MSTEAMS_APP_PASSWORD holds a current (non-expired) client secret.
What happened: The messaging endpoint URL, its TLS certificate, or the manifest’s bot ID does not line up.How to fix it: Verify the endpoint URL ends in /channels/msteams/api/messages, that the certificate presented publicly is valid, and that the bot ID in the Teams manifest equals your appId.

Later additions

The v1 Teams channel focuses on chat, cards, reactions, edit/delete, media, and proactive delivery. These are documented as not-yet-shipped so you can plan around them:
  • Native token-by-token DM streaming (the agent sends chunked message blocks today)
  • Uploading files and videos via the FileConsent / SharePoint flow
  • A Microsoft Graph change-notification event source
  • Sovereign and national clouds (the channel targets the public cloud)

Delivery Infrastructure

How streaming, typing indicators, and retry logic work under the hood.

Multiple users & teams

Run one install for a whole team - private per person, isolated per agent.

All Channels

Compare all 11 supported platforms side by side.

Agent Configuration

Set up your agent’s personality, tools, and behavior.

Secret Management

Learn how to manage API keys and tokens securely.