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.

Info

Two ways to run this connector:

  • Run it yourself (Free + paid) — pip install 'cortexdb-connectors[teams]' then cortexdb-sync sync teams. The connector reads your token from ~/.cortexdb/state.json (written by cortexdb 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.
Warning

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:

PermissionWhy
ChannelMessage.Read.AllRead channel messages and threaded replies
Chat.Read.AllRead 1:1 and group chat messages (only if TEAMS_INCLUDE_CHATS=1)
User.Read.AllResolve message authors to names/emails
TeamMember.Read.AllResolve channel membership for visibility mapping

Have an admin grant tenant-wide consent, then create a client secret and copy it.

Note

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

  1. Sign in at cortexdb.ai/login.
  2. Settings → Connectors → Add Connector → Microsoft Teams.
  3. Paste the tenant ID, client ID, and client secret.
  4. Pick the teams to sync (comma-separated IDs; leave empty for every team the app can see).
  5. Set the scope template. Default: org:<your-org>/source:teams/team:{team_id}/channel:{channel_id}.
  6. Click Start sync.

What gets written

Every episode's content is the message body, with HTML stripped to plain text.

EventEpisode typeNotes
Channel messagemessageAuthor resolved to display name + userPrincipalName
Reply in threadmessageparent_id and thread_id link it to the parent message
1:1 / group chat messagemessageOnly when TEAMS_INCLUDE_CHATS=1; scope template adapted
Message posted in a meeting chatmeetingTagged meeting. This is the chat message, not a transcript
Message carrying an attachmentdocumentTagged 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

SourceVisibility
Standard (public) channelorganization
Private channelrestricted
1:1 / group chatprivate, 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:

CapabilityStatus in 0.2.20
Meeting transcriptsNot implemented. Messages posted in a meeting chat are captured; the transcript is not fetched
File / attachment contentsNot 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 queriesNot 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).

See also