Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,16 @@ bollard = "0.18"
# Semver parsing (for update version comparison)
semver = "1"

# JWT validation for inbound Bot Framework requests (ring-based, no openssl)
jsonwebtoken = "9.3"

# Skill installation
zip = "2"
tempfile = "3"

# Icon resizing for the generated Teams app package (already in the lockfile transitively).
image = { version = "0.25", default-features = false, features = ["png"] }

# Prometheus metrics (optional, behind "metrics" feature)
prometheus = { version = "0.13", optional = true }
pdf-extract = "0.10.0"
Expand Down
3 changes: 2 additions & 1 deletion docs/content/docs/(messaging)/messaging.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Messaging
description: How Spacebot connects to Discord, Slack, Telegram, Twitch, Email, and webhooks.
description: How Spacebot connects to Discord, Slack, Microsoft Teams, Telegram, Twitch, Email, and webhooks.
---

# Messaging
Expand All @@ -13,6 +13,7 @@ Spacebot connects to chat platforms so your agent can talk to people where they
|----------|--------|-------------|
| [Discord](/docs/discord-setup) | Supported | Bot token + gateway connection |
| [Slack](/docs/slack-setup) | Supported | Bot token + app token via Socket Mode |
| [Microsoft Teams](/docs/teams-setup) | Supported | Azure Bot + Entra app, public HTTPS endpoint |
| [Telegram](/docs/telegram-setup) | Supported | Bot token via BotFather |
| [Twitch](/docs/twitch-setup) | Supported | OAuth token via Twitch IRC |
| [Email](/docs/email-setup) | Supported | IMAP polling + SMTP replies |
Expand Down
2 changes: 1 addition & 1 deletion docs/content/docs/(messaging)/meta.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"title": "Messaging",
"pages": ["messaging", "discord-setup", "slack-setup", "telegram-setup", "twitch-setup", "email-setup"]
"pages": ["messaging", "discord-setup", "slack-setup", "teams-setup", "telegram-setup", "twitch-setup", "email-setup"]
}
159 changes: 159 additions & 0 deletions docs/content/docs/(messaging)/teams-setup.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
---
title: Teams Setup
description: Connect Spacebot to Microsoft Teams.
---

# Teams Setup

Connect Spacebot to Microsoft Teams as a bot. Teams talks to your bot over the Azure Bot Service, so this takes a bit more setup than the other channels: an Entra (Azure AD) app registration, an Azure Bot resource, a Teams app package, and a public HTTPS endpoint Microsoft can reach.

You need three values from Azure — an **App ID**, a **Client Secret**, and a **Tenant ID** — plus a public URL that forwards to Spacebot.

<Callout type="warn">
Teams requires a **public HTTPS endpoint**. The bot does not connect outbound like Slack; the Azure Bot Service delivers messages by POSTing to a URL you register. A localhost bind is not reachable. Put Spacebot behind a reverse proxy (Caddy, nginx) or a tunnel (cloudflared) that terminates TLS and forwards to the bot's port.
</Callout>

## Step 1: Register an Entra app

In the [Azure portal](https://portal.azure.com), go to **Microsoft Entra ID** → **App registrations** → **New registration**.

Name it (e.g. "Spacebot"), pick the supported account types for your org, and register.

From the app's **Overview**, copy the **Application (client) ID** and the **Directory (tenant) ID**.

Then go to **Certificates & secrets** → **New client secret**, create one, and copy its **Value** immediately (it is shown only once). This is your **Client Secret**.

## Step 2: Create the Azure Bot

Create an **Azure Bot** resource (the free **F0** SKU is enough). Point it at the app you just registered (use the existing App ID rather than creating a new identity).

In the bot's **Configuration**, set the **Messaging endpoint** to your public URL with the `/api/messages` path:

```
https://your-public-host/api/messages
```

Then open **Channels** and add the **Microsoft Teams** channel.

## Step 3: Build and install the Teams app

Teams needs an app package (a zip with a `manifest.json` and two icons).

<Tabs items={["Generate from Spacebot", "Manual"]}>
<Tab value="Generate from Spacebot">

1. In **Settings → Channels**, open the Microsoft Teams card and enter your **App ID** (Step 1).
2. Click **Download Teams app package**. Spacebot builds a correct `manifest.json` (with the right schema, a distinct app id, and the Spacebot icon) and downloads `spacebot-teams-app.zip`.
3. Upload it in Teams (**Apps → Manage your apps → Upload an app**).

The package uses the default Spacebot icon. To use your own, unzip it, replace `icon-color.png` (192×192) and `icon-outline.png` (32×32, transparent), and re-zip.

</Tab>
<Tab value="Manual">

Build the package by hand if you prefer. The `manifest.json`:

<Callout type="info">
The manifest `id` (the Teams app id) must be its **own** GUID, distinct from the bot's App ID. `bots[].botId` is the App ID from Step 1. Scopes are lowercase (`personal`, `team`, `groupchat`); `validDomains` and `accentColor` are required; do **not** add a `packageName` key (schema 1.17 rejects it).
</Callout>

```json
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.17/MicrosoftTeams.schema.json",
"manifestVersion": "1.17",
"version": "1.0.0",
"id": "GENERATE-A-FRESH-GUID",
"developer": { "name": "Spacebot", "websiteUrl": "https://example.com", "privacyUrl": "https://example.com", "termsOfUseUrl": "https://example.com" },
"icons": { "color": "icon-color.png", "outline": "icon-outline.png" },
"name": { "short": "Spacebot", "full": "Spacebot — AI assistant" },
"description": { "short": "AI assistant powered by Spacebot.", "full": "Chat with your Spacebot agent in Microsoft Teams." },
"accentColor": "#6264A7",
"bots": [{ "botId": "YOUR-APP-ID", "scopes": ["personal", "team", "groupchat"], "supportsFiles": false, "isNotificationOnly": false }],
"validDomains": []
}
```

Zip `manifest.json` + `icon-color.png` (192×192) + `icon-outline.png` (32×32) at the **root** of the archive.

</Tab>
</Tabs>

Most tenants require admin approval: approve it in the **Teams admin center** under **Manage apps**, where you can also restrict who may install it.

## Step 4: Configure Spacebot

<Tabs items={["Spacebot UI", "TOML Config"]}>
<Tab value="Spacebot UI">

1. Open your Spacebot dashboard
2. Go to **Settings** → **Channels**
3. Click **Setup** on the Microsoft Teams card
4. Paste your **App ID**, **Client Secret**, and **Tenant ID** from the steps above
5. Click **Save**

Spacebot writes the config and starts the adapter. Make sure the bot's port is reachable through your public HTTPS endpoint.

</Tab>
<Tab value="TOML Config">

Add a `[messaging.teams]` block to your config. Keep the client secret out of the file by referencing an environment variable.

```toml
[messaging.teams]
enabled = true

# App ID and Tenant ID from Step 1 (not secret).
app_id = "00000000-0000-0000-0000-000000000000"
tenant_id = "00000000-0000-0000-0000-000000000000"

# Reference the secret from the environment; never hardcode it.
client_secret = "env:TEAMS_CLIENT_SECRET"

# Inbound listener. The reverse proxy / tunnel forwards here.
port = 3979
bind = "0.0.0.0"

# Teams user IDs allowed to DM the bot. "*" allows everyone.
dm_allowed_users = ["*"]

[[bindings]]
agent_id = "main"
channel = "teams"
```

Start Spacebot with the secret in the environment:

```bash
TEAMS_CLIENT_SECRET="your-secret-value" spacebot start
```

</Tab>
</Tabs>

## DM permissions

Channel and group messages are open: the bot replies wherever it is @mentioned. Direct messages are **fail-closed** — an empty `dm_allowed_users` blocks every DM.

- To allow specific people, list their Teams user IDs (the `29:...` MRI strings).
- To allow everyone (org-wide deployments), set `dm_allowed_users = ["*"]`.

<Callout type="info">
You usually do not know a user's `29:...` MRI up front. The simplest path is to **@mention the bot in a channel** (channel messages are not gated), or set the `"*"` wildcard. To allowlist one person, send a DM once (it is dropped), read the dropped `sender_id` from the logs, and add that value.
</Callout>

## What works

| Capability | Notes |
|------------|-------|
| Text replies | Plain text in DMs, channels, and group chats |
| @mentions | The bot responds when @mentioned in a channel or group |
| Adaptive Cards | Structured cards rendered from rich replies |
| Typing indicator | Shown while the bot is working |
| Inbound files and images | Delivered to the agent (personal scope) |
| Buttons | Adaptive Card buttons; a click comes back to the agent |

## Limitations

- **One Teams bot per instance.** A single default `[messaging.teams]` adapter is supported. Named multi-bot instances are not yet available.
- **Files are personal-scope.** Inbound file attachments work in 1:1 chats; channel files require Microsoft Graph and are out of scope.
- **Rotate the client secret** before relying on it in production, and keep it in the environment rather than the config file.
5 changes: 5 additions & 0 deletions interface/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2163,6 +2163,11 @@ export const api = {
);
},

teamsAppPackageUrl: (appId: string) => {
const params = new URLSearchParams({ app_id: appId });
return `${getApiBase()}/messaging/teams/app-package?${params.toString()}`;
},

attachmentUrl: (agentId: string, attachmentId: string, opts?: { thumbnail?: boolean; download?: boolean }) => {
const params = new URLSearchParams();
if (opts?.thumbnail) params.set("thumbnail", "true");
Expand Down
Loading