You don’t need to understand the technical details to use this feature. The configuration examples below are copy-paste ready.
Prerequisites
- Comis installed and running (Quickstart)
- 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
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 Your messaging endpoint becomes
*.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: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
- Teams Toolkit CLI
- Azure Portal (manual)
The Teams Toolkit CLI
provisions the app registration, the bot, and a starter manifest without
clicking through the Azure portal.
- Sign in to your tenant with the CLI
- Provision a bot registration — the CLI creates the Entra application and returns its application (client) ID and a generated client secret
- Set the bot’s messaging endpoint to
https://your-host/channels/msteams/api/messages - Note the directory (tenant) ID of the tenant you signed in 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
(Set the secret in your
~/.comis/config.yaml):~/.comis/.env file: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 withauthMode. 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.
- secret (default)
- certificate
- managedIdentity
A client secret. Simplest to set up; the secret must be rotated before it
expires.
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).
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 doctorruns 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 achannel:inbound_silentevent and a WARN that surfaces as acomis fleethealth 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
-
certificateandmanagedIdentityauth modes each start cleanly;comis doctorshows 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 attest/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:
Wiring (as the operator, on the test box):
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
@mentionsand detect when it is mentioned
Platform limits
Troubleshooting
Bot can send messages but never receives any
Bot can send messages but never receives any
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.Inbound requests are rejected with an authentication error
Inbound requests are rejected with an authentication error
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.Teams shows an error even though the endpoint is reachable
Teams shows an error even though the endpoint is reachable
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.
