Skip to content

[FEATURE] Messaging integration for Microsoft Teams #404

@diegohb

Description

@diegohb

Here is the drafted GitHub feature request issue, grounded entirely in the actual codebase architecture:


Feature Request: Microsoft Teams Messaging Integration

Title: feat(messaging): add Microsoft Teams adapter

Labels: enhancement, messaging, new-adapter


Summary

Spacebot currently supports five native messaging platforms: Discord, Slack, Telegram, Twitch, and Webchat, plus Email and Webhook. 1

This issue requests adding Microsoft Teams as a first-class messaging adapter, following the exact same pattern used by the existing adapters. Microsoft Teams is one of the most widely used enterprise collaboration platforms in the world, and supporting it would unlock Spacebot for corporate/enterprise multi-agent deployments.


Motivation

Spacebot is explicitly built for teams and multi-user environments. The README states:

"For teams — connect it to Slack. Each channel gets a dedicated conversation with shared memory."

Microsoft Teams users deserve equivalent first-class support. Enterprise teams running on Microsoft 365 currently have no path to deploying Spacebot natively in their environment, and the existing Webhook adapter is too low-level for a full integration (no typing indicators, no rich cards, no per-channel permission filtering, etc.).


Implementation Plan

The integration follows the established adapter pattern precisely. Here is what needs to be built:

1. New Adapter File: src/messaging/teams.rs

A new TeamsAdapter struct must implement the Messaging trait defined in src/messaging/traits.rs: 2

The trait requires:

  • name(&self) -> &str — return the runtime key (e.g. "teams" or "teams:ops")
  • start(&self) -> Result<InboundStream> — connect to Teams via the Bot Framework SDK and begin receiving messages
  • respond(&self, message, response) -> Result<()> — send outbound responses using Adaptive Cards, plain text, or file uploads
  • send_status(&self, message, status) -> Result<()> — show typing indicators via the Bot Framework's sendActivity with typing type
  • broadcast(&self, target, response) -> Result<()> — proactive messaging to a Teams channel
  • fetch_history(&self, message, limit) -> Result<Vec<HistoryMessage>> — backfill recent messages using the Microsoft Graph API
  • health_check(&self) -> Result<()> — verify the Bot Framework credentials are valid
  • shutdown(&self) -> Result<()> — gracefully disconnect

Because the Messaging trait has a blanket implementation into MessagingDyn, any type implementing Messaging is automatically usable via Arc<dyn MessagingDyn> in the MessagingManager: 3

2. Declare the Module in src/messaging.rs

pub mod teams;

This mirrors how Slack and Discord are declared today: 1

3. New Config Types in src/config/types.rs

Following the pattern of SlackConfig / SlackInstanceConfig: 4

A TeamsConfig and TeamsInstanceConfig need to be added, holding:

  • enabled: bool
  • app_id: String — Azure AD App (Client) ID
  • app_password: String — Bot Framework client secret
  • tenant_id: Option<String> — for single-tenant bots
  • dm_allowed_users: Vec<String> — AAD Object IDs of users allowed to DM
  • instances: Vec<TeamsInstanceConfig> — for multi-tenant named instances

SystemSecrets must be implemented (matching the Slack pattern) to register TEAMS_APP_ID and TEAMS_APP_PASSWORD as system-category secrets that are never injected into worker subprocesses.

4. Add teams Field to MessagingConfig

MessagingConfig in src/config/types.rs needs a new optional field:

pub teams: Option<TeamsConfig>,

Today it holds: 5

5. TOML Schema: src/config/toml_schema.rs

A TomlTeamsConfig struct needs to be added and wired into TomlMessagingConfig: 6

6. Permissions: src/config/permissions.rs

A TeamsPermissions struct (following the SlackPermissions / TelegramPermissions pattern) should be added: 7

Teams-specific filters:

  • tenant_filter: Option<Vec<String>> — restrict to specific Azure tenants
  • team_filter: Option<Vec<String>> — restrict to specific Teams (group IDs)
  • channel_filter: HashMap<String, Vec<String>> — team_id → allowed channel IDs
  • dm_allowed_users: Vec<String> — AAD user object IDs

The Arc<ArcSwap<TeamsPermissions>> pattern used by Slack/Discord enables hot-reloadable permissions without restarting the adapter connection.

7. Binding Extension

The Binding struct currently has guild_id (Discord), workspace_id (Slack), and chat_id (Telegram): 5

A new field team_id: Option<String> should be added to Binding (and its TOML/API counterparts) to filter bindings by Microsoft Teams group/team ID.

8. MessagingManager Registration

The adapter is registered using the existing register() / register_and_start() API in MessagingManager, which supports hot-reload and retry with exponential backoff at no extra implementation cost: 8


Proposed config.toml Interface

[messaging.teams]
app_id     = "env:TEAMS_APP_ID"
app_password = "env:TEAMS_APP_PASSWORD"
tenant_id  = "your-azure-tenant-id"   # optional; omit for multi-tenant

# Optional: named instances for multi-tenant or multi-bot setups
[[messaging.teams.instances]]
name         = "eng-bot"
app_id       = "env:TEAMS_ENG_APP_ID"
app_password = "env:TEAMS_ENG_APP_PASSWORD"

[[bindings]]
agent_id  = "my-agent"
channel   = "teams"
team_id   = "your-teams-group-id"      # optional; omit for all teams
channel_ids = ["channel-id-1"]          # optional; omit for all channels

Technology / SDK

The recommended approach is to use Microsoft's Bot Framework for Rust via REST (the SDK is not Rust-native), or the Microsoft Graph REST API using reqwest + tokio. The adapter would:

  1. Inbound: Expose an HTTPS webhook endpoint that Teams posts activity payloads to (similar to the Slack Socket Mode listener in src/messaging/slack.rs). 9

  2. Outbound: Call the Bot Framework REST API (https://smba.trafficmanager.net/...) using the OAuth 2.0 client_credentials flow to obtain a bearer token, then POST Activity objects — plain text, Adaptive Cards, or file attachments.

  3. Typing indicator: Send an Activity of type "typing" before the response, matching the send_status contract: 10

  4. History backfill: Call Microsoft Graph GET /teams/{team-id}/channels/{channel-id}/messages to implement fetch_history.

Suggested Cargo dependencies:

# In Cargo.toml
reqwest  = { version = "0.12", features = ["json", "rustls-tls"] }
# (reqwest is likely already present; no new HTTP dep needed)

No heavy SDK needed — the Bot Framework REST protocol is well-documented and straightforward with reqwest + serde_json.


Outbound Response Mapping

The existing OutboundResponse variants map naturally to Teams:

OutboundResponse Teams equivalent
Text(s) Plain text Activity
File { filename, data, ... } Teams file attachment via Graph upload
Reaction(emoji) Message like/reaction (Graph API)
RichMessage { blocks, ... } Adaptive Card payload
StreamChunk / StreamEnd Edit the message via PUT activity

Reference Links


Acceptance Criteria

  • TeamsAdapter implements the Messaging trait (all 7 methods)
  • TeamsConfig / TeamsInstanceConfig added to src/config/types.rs with SystemSecrets impl
  • teams field added to MessagingConfig
  • TomlTeamsConfig added to src/config/toml_schema.rs
  • TeamsPermissions with from_config / from_instance_config added to src/config/permissions.rs
  • team_id field added to Binding (config, TOML schema, API, TypeScript client)
  • Hot-reload support: permissions swap via Arc<ArcSwap<TeamsPermissions>> without reconnect
  • Named instance support (e.g. messaging.teams.instances[].name = "ops")
  • Inbound: plain text messages, @mention events, and file attachments parsed into InboundMessage
  • Outbound: text (chunked), Adaptive Cards, file uploads, typing indicators, and reactions
  • fetch_history implemented via Microsoft Graph API
  • Credentials (app_id, app_password) registered in SystemSecrets and never injected into worker subprocesses (matching the security model of all existing adapters)
  • TEAMS_APP_ID / TEAMS_APP_PASSWORD env vars supported
  • Integration docs added to docs/content/docs/(messaging)/
  • just preflight and just gate-pr pass

Notes

The MessagingManager's built-in retry-with-exponential-backoff means the Teams adapter gets automatic resilience to transient Bot Framework outages for free. 11

The existing security model (environment sanitisation, system-secret isolation, output scrubbing) applies automatically to the Teams adapter with no extra work, as long as TeamsConfig implements SystemSecrets correctly — matching how SlackConfig does it today. 12


Notes

  • The entire feature is additive — no changes to any existing adapter or the core dispatch loop are required. The MessagingManager's register() and register_and_start() APIs already handle everything needed. 13

  • The Messaging + MessagingDyn blanket-impl pattern means no changes to the manager's routing, respond, or broadcast code paths are needed — a Teams adapter that passes the trait bounds is wired in automatically. 14

  • The Teams Bot Framework uses an inbound webhook model (Teams POSTs to your server), unlike Slack Socket Mode (your server connects outward). This means the Teams adapter will need to register an HTTPS endpoint — similar in structure to the existing WebhookAdapter. 15

  • The OutboundResponse::RichMessage variant (which Discord uses for embeds and Slack uses for Block Kit) maps directly to Adaptive Cards on Teams — no new OutboundResponse variants should be needed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions