Ingest Microsoft Teams channel messages, threaded replies, and 1:1/group chats into CortexDB.
Microsoft Teams Connector
Captures Teams channel and chat messages via the Microsoft Graph v1.0 API, polled
incrementally on lastModifiedDateTime.
Two ways to run this connector:
- Run it yourself (Free + paid) —
pip install 'cortexdb-connectors[teams]'thencortexdb-sync sync teams. The connector reads your token from~/.cortexdb/state.json(written bycortexdb init) or from env vars. - Managed sync (Starter and up) — CortexDB runs the same connector as a worker on its infrastructure. Configure it once from your dashboard; CortexDB handles scheduling, retries, backfill, and idempotency.
Not ingested by this connector. Meeting transcripts, file/attachment contents,
Graph change notifications, and delta queries are not implemented in the
cortexdb-connectors package as of 0.2.20. Messages are collected by polling
only. See What is not ingested below — it also determines
which Graph permissions you actually need to grant.
1. Prepare credentials in Microsoft Entra (Azure AD)
Azure portal → App registrations → New registration. Add Microsoft Graph application permissions:
| Permission | Why |
|---|---|
ChannelMessage.Read.All | Read channel messages and threaded replies |
Chat.Read.All | Read 1:1 and group chat messages (only if TEAMS_INCLUDE_CHATS=1) |
User.Read.All | Resolve message authors to names/emails |
TeamMember.Read.All | Resolve channel membership for visibility mapping |
Have an admin grant tenant-wide consent, then create a client secret and copy it.
Grant only the four permissions above. Earlier revisions of this page also listed
OnlineMeetingTranscript.Read.All; the connector never calls the transcript API,
so that consent is not required and should not be requested.
2. Configure in your CortexDB dashboard
- Sign in at cortexdb.ai/login.
- Settings → Connectors → Add Connector → Microsoft Teams.
- Paste the tenant ID, client ID, and client secret.
- Pick the teams to sync (comma-separated IDs; leave empty for every team the app can see).
- Set the scope template. Default:
org:<your-org>/source:teams/team:{team_id}/channel:{channel_id}. - Click Start sync.
What gets written
Every episode's content is the message body, with HTML stripped to plain text.
| Event | Episode type | Notes |
|---|---|---|
| Channel message | message | Author resolved to display name + userPrincipalName |
| Reply in thread | message | parent_id and thread_id link it to the parent message |
| 1:1 / group chat message | message | Only when TEAMS_INCLUDE_CHATS=1; scope template adapted |
| Message posted in a meeting chat | meeting | Tagged meeting. This is the chat message, not a transcript |
| Message carrying an attachment | document | Tagged file_share, with metadata.attachment_count. The file itself is not fetched |
@-mentions are recorded as display names in metadata.mentions. High-importance
messages are tagged high_importance.
idempotency_key equals the external id, which is stable per message:
teams:channel:<team_id>:<channel_id>:<message_id>
teams:chat:<chat_id>:<message_id>
Because that key is stable rather than version-aware, an edited message dedupes against its first ingest instead of being retained as a new version.
Visibility
| Source | Visibility |
|---|---|
| Standard (public) channel | organization |
| Private channel | restricted |
| 1:1 / group chat | private, with the chat's member ids as the allow-list |
What is not ingested
These are genuine gaps in the shipped package, not configuration you are missing:
| Capability | Status in 0.2.20 |
|---|---|
| Meeting transcripts | Not implemented. Messages posted in a meeting chat are captured; the transcript is not fetched |
| File / attachment contents | Not implemented. A message with an attachment is typed document and counted, but no bytes are uploaded to /v1/blobs |
| Graph change notifications (webhooks) | Not implemented. There is no Teams webhook receiver |
| Delta queries | Not implemented. Sync uses the lastModifiedDateTime filter with @odata.nextLink paging |
If you need attachment contents indexed today, the Jira, Slack and Freshdesk connectors do ingest attachments through the blob pipeline.
Run it yourself
If you'd rather host the connector yourself instead of using CortexDB's managed worker, every connector ships in the cortexdb-connectors PyPI package:
pip install 'cortexdb-connectors[teams]'
# Step 1: get a CortexDB token + actor (one-time, free tier)
pip install cortexdb-cli
cortexdb init
# Step 2: supply the connector's third-party credentials
export TEAMS_TENANT_ID=... # required
export TEAMS_CLIENT_ID=... # required
export TEAMS_CLIENT_SECRET=... # required
export TEAMS_TEAM_IDS=team-a,team-b # required for channel ingestion
export TEAMS_INCLUDE_CHATS=0 # 1 to also sync 1:1 / group chats
# Step 3: one-shot sync, or `watch` for a poll loop
cortexdb-sync sync teams
cortexdb-sync watch teams --interval 60
cortexdb-sync reads your CortexDB token + actor from ~/.cortexdb/state.json automatically. Cursor state is persisted in ~/.cortexdb/sync_state.json, so re-running picks up where the last cycle left off.
Rate limiting: the connector honours Retry-After on Graph 429s with up to 5
retries, refreshes its token on a mid-sync 401, and paces requests at 1 req/sec.
The first run backfills backfill_days (default 30).