|
| 1 | +--- |
| 2 | +title: Teams Setup |
| 3 | +description: Connect Spacebot to Microsoft Teams. |
| 4 | +--- |
| 5 | + |
| 6 | +# Teams Setup |
| 7 | + |
| 8 | +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. |
| 9 | + |
| 10 | +You need three values from Azure — an **App ID**, a **Client Secret**, and a **Tenant ID** — plus a public URL that forwards to Spacebot. |
| 11 | + |
| 12 | +<Callout type="warn"> |
| 13 | +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. |
| 14 | +</Callout> |
| 15 | + |
| 16 | +## Step 1: Register an Entra app |
| 17 | + |
| 18 | +In the [Azure portal](https://portal.azure.com), go to **Microsoft Entra ID** → **App registrations** → **New registration**. |
| 19 | + |
| 20 | +Name it (e.g. "Spacebot"), pick the supported account types for your org, and register. |
| 21 | + |
| 22 | +From the app's **Overview**, copy the **Application (client) ID** and the **Directory (tenant) ID**. |
| 23 | + |
| 24 | +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**. |
| 25 | + |
| 26 | +## Step 2: Create the Azure Bot |
| 27 | + |
| 28 | +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). |
| 29 | + |
| 30 | +In the bot's **Configuration**, set the **Messaging endpoint** to your public URL with the `/api/messages` path: |
| 31 | + |
| 32 | +``` |
| 33 | +https://your-public-host/api/messages |
| 34 | +``` |
| 35 | + |
| 36 | +Then open **Channels** and add the **Microsoft Teams** channel. |
| 37 | + |
| 38 | +## Step 3: Build and install the Teams app |
| 39 | + |
| 40 | +Teams needs an app package (a zip with a `manifest.json` and two icons) to surface the bot to users. |
| 41 | + |
| 42 | +<Callout type="info"> |
| 43 | +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`), and `validDomains` plus an `accentColor` are required. |
| 44 | +</Callout> |
| 45 | + |
| 46 | +Upload the package in Teams (**Apps** → **Manage your apps** → **Upload an app**). Most tenants require admin approval: approve it in the **Teams admin center** under **Manage apps**, where you can also restrict who may install it. |
| 47 | + |
| 48 | +## Step 4: Configure Spacebot |
| 49 | + |
| 50 | +Add a `[messaging.teams]` block to your config. Keep the client secret out of the file by referencing an environment variable. |
| 51 | + |
| 52 | +```toml |
| 53 | +[messaging.teams] |
| 54 | +enabled = true |
| 55 | + |
| 56 | +# App ID and Tenant ID from Step 1 (not secret). |
| 57 | +app_id = "00000000-0000-0000-0000-000000000000" |
| 58 | +tenant_id = "00000000-0000-0000-0000-000000000000" |
| 59 | + |
| 60 | +# Reference the secret from the environment; never hardcode it. |
| 61 | +client_secret = "env:TEAMS_CLIENT_SECRET" |
| 62 | + |
| 63 | +# Inbound listener. The reverse proxy / tunnel forwards here. |
| 64 | +port = 3979 |
| 65 | +bind = "0.0.0.0" |
| 66 | + |
| 67 | +# Teams user IDs allowed to DM the bot. "*" allows everyone. |
| 68 | +dm_allowed_users = ["*"] |
| 69 | + |
| 70 | +[[bindings]] |
| 71 | +agent_id = "main" |
| 72 | +channel = "teams" |
| 73 | +``` |
| 74 | + |
| 75 | +Start Spacebot with the secret in the environment: |
| 76 | + |
| 77 | +```bash |
| 78 | +TEAMS_CLIENT_SECRET="your-secret-value" spacebot start |
| 79 | +``` |
| 80 | + |
| 81 | +## DM permissions |
| 82 | + |
| 83 | +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. |
| 84 | + |
| 85 | +- To allow specific people, list their Teams user IDs (the `29:...` MRI strings). |
| 86 | +- To allow everyone (org-wide deployments), set `dm_allowed_users = ["*"]`. |
| 87 | + |
| 88 | +<Callout type="info"> |
| 89 | +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. |
| 90 | +</Callout> |
| 91 | + |
| 92 | +## What works |
| 93 | + |
| 94 | +| Capability | Notes | |
| 95 | +|------------|-------| |
| 96 | +| Text replies | Plain text in DMs, channels, and group chats | |
| 97 | +| @mentions | The bot responds when @mentioned in a channel or group | |
| 98 | +| Adaptive Cards | Structured cards rendered from rich replies | |
| 99 | +| Typing indicator | Shown while the bot is working | |
| 100 | +| Inbound files and images | Delivered to the agent (personal scope) | |
| 101 | +| Buttons | Adaptive Card buttons; a click comes back to the agent | |
| 102 | + |
| 103 | +## Limitations |
| 104 | + |
| 105 | +- **One Teams bot per instance.** A single default `[messaging.teams]` adapter is supported. Named multi-bot instances are not yet available. |
| 106 | +- **Files are personal-scope.** Inbound file attachments work in 1:1 chats; channel files require Microsoft Graph and are out of scope. |
| 107 | +- **Rotate the client secret** before relying on it in production, and keep it in the environment rather than the config file. |
0 commit comments