# Teams Bot

This guide walks through shipping a Microsoft Teams chatbot that:

* Is **pinned to the Teams left rail** (looks and feels like Microsoft Copilot's side chat).
* **Receives every user message** as a webhook trigger into a StackAI workflow.
* **Posts replies back** into the same conversation thread.

End-to-end you will need:

* An Azure subscription (or Pay-As-You-Go) and Microsoft 365 tenant where you can register apps.
* Admin permission to upload custom Teams apps to your org (or at least to your own Teams account for sideloading).
* A StackAI workspace with the **Microsoft Teams Bot** integration available.

<figure><img src="/files/qiuM4nEmKWjtF6z763li" alt=""><figcaption></figcaption></figure>

***

### Architecture at a glance

```
Teams client (left-rail pinned app)
        │  user types a message
        ▼
Azure Bot Service (Bot Framework)
        │  HTTPS Activity payload
        ▼
StackAI workflow trigger: "On New Teams Message"
        │  workflow runs (LLM, retrieval, tools, …)
        ▼
StackAI action: "Reply to Message"
        │  Bot Framework REST call (uses serviceUrl + conversationId)
        ▼
Teams client (reply lands in same thread)
```

Three Microsoft pieces and one StackAI piece:

1. **Microsoft Entra app registration** — gives the bot an identity (App ID + client secret).
2. **Azure Bot resource** — registers the bot with Bot Framework and turns on the Teams channel.
3. **Teams app manifest** — declares a personal-scope bot so users can pin it.
4. **StackAI workflow** — the brain.

***

### Step 1 — Register an Entra (Azure AD) application

This gives the bot its identity.

1. Go to **Azure Portal → Microsoft Entra ID → App registrations → New registration**.
2. Fill in:
   * **Name**: e.g. `StackAI Teams Bot`.
   * **Supported account types**: choose based on who should be able to use the bot:
     * "Single tenant" — only your org.
     * "Multi-tenant" — recommended if you'll distribute to other tenants.
   * **Redirect URI**: leave this field blank.
3. Click **Register**.
4. On the overview page, copy and save:
   * **Application (client) ID** → this is the **App ID**.
   * **Directory (tenant) ID** → this is the **Tenant ID**.
5. Go to **Certificates & secrets → New client secret**:
   * Description: `StackAI Bot`.
   * Expiry: 24 months (rotate before expiry).
   * Click **Add**, then **immediately copy the Value** (not the Secret ID). This is the **Client Secret** — Azure will never show it again.

Save these three values somewhere safe:

```
APP_ID         = <Application (client) ID>
TENANT_ID      = <Directory (tenant) ID>
CLIENT_SECRET  = <Client secret value>
```

***

### Step 2 — Create the Azure Bot resource

This is the Bot Framework registration that lets Teams talk to your bot endpoint.

<figure><img src="/files/S9QhjcL89fioveA2synV" alt=""><figcaption></figcaption></figure>

1. In the [Azure Portal](https://portal.azure.com/), search **Azure Bot** → **Create**.
2. Fill in:
   * **Bot handle**: a unique name like `stackai-teams-bot-<orgname>`.
   * **Subscription** and **Resource group**: pick or create.
   * **Pricing tier**: `F0` (free) is fine for development; use `S1` for production.
   * **Type of App**: **Multi Tenant** (or Single Tenant — must match what you chose in Step 1).
   * **Creation type**: **Use existing app registration**.
   * **App ID**: paste the App ID from Step 1.
3. Click **Review + create → Create**. Wait for deployment.
4. After deployment, open the new **Azure Bot** resource. You'll point its messaging endpoint at StackAI in Step 4.
5. Go to the bot's **Channels** blade in **Settings** → click **Microsoft Teams** → accept terms → **Apply**.
6. In the same **Channels** tab, you should see the Teams channel show **Healthy**.

***

### Step 3 — Create the connection in StackAI

Before configuring the messaging endpoint, you need the StackAI bot connection so you can grab its webhook URL.

1. In StackAI, open **Integrations → Microsoft Teams Bot → Connect**.
2. Fill in:
   * **App ID** → from Step 1.
   * **Client Secret** → from Step 1.
   * **Tenant ID** → from Step 1 (use `common` if you registered the app as multi-tenant and want it to work across tenants).
3. Save the connection. StackAI will validate the credentials by minting a Bot Framework token.

Keep this tab open — you'll come back to it once the workflow trigger exists.

***

### Step 4 — Build the StackAI workflow

The workflow is the brain. At minimum it needs a Teams trigger and a Teams reply action.

#### 4a. Create the workflow

1. In StackAI, **New Project → blank workflow**.
2. Add a **Trigger node** → choose **Microsoft Teams Bot → On New Teams Message**.
   * Select the connection you created in Step 3.
   * Save.

> Tip: if you want the bot to respond only when **@mentioned in a channel**, use the **On Bot Mentioned** trigger instead. For 1:1 personal chat, **On New Teams Message** is correct.

3. Add an LLM node (or whatever workflow logic you want).
   * Pipe the trigger's `message_text` into the prompt.
   * Other useful trigger fields: `conversation_id`, `sender_id`, `service_url`, `activity_id`.
4. Add an action node → **Microsoft Teams Bot → Reply to Message**.
   * Connection: same as the trigger.
   * **Conversation ID**, **Service URL**, **Reply to Activity ID**: map from the trigger output. The integration uses these to thread the reply correctly.
   * **Message**: pipe in the LLM output.
5. Save and **publish** the workflow.

#### 4b. Wire the messaging endpoint to Azure Bot

The Microsoft Teams Bot integration uses a **shared, fixed webhook URL** for every Teams bot — there is no per-trigger URL generated in the StackAI UI. StackAI routes each inbound activity to the correct workflow using the App ID / Tenant ID on your connection (Step 3).

1. Back in the Azure Portal → your Azure Bot resource → **Settings → Configuration**.
2. **Messaging endpoint**: paste this URL exactly:

   ```
   https://api.stack-ai.com/v1/webhooks/microsoft-teams
   ```
3. Click **Apply**.

Azure will now send every Teams Activity (message, mention, etc.) to that URL, and StackAI will dispatch each one to the workflow whose connection matches the bot's App ID.

***

### Step 5 — Create the Teams app manifest

This is what makes the bot show up as a pinnable personal app in Teams. The easiest path is **Teams Developer Portal**.

1. Go to <https://dev.teams.microsoft.com> → **Apps → New app**.
2. Give it a name (`StackAI Assistant` works) → **Add**.
3. Under **Basic information**, fill in:
   * **App ID**: click the dice icon to generate a new GUID (this is the *Teams app* ID — different from the Bot App ID).
   * **Short / long descriptions**.
   * **Developer info**, **Website**, **Privacy / TOS URLs** (required even for internal apps — point at any reachable HTTPS page).
   * **App icons**: 192×192 color + 32×32 outline. Required.
4. Under **App features → Bot → Create new bot** (or pick existing):
   * When prompted for a Bot ID, **paste the App ID from Step 1** (the Entra app — same one used by your Azure Bot resource).
   * **Scope**: enable **Personal** (and **Team** / **Group chat** if you also want it usable in those contexts).
5. Save.

#### Manifest sanity check

Under **App features → Bot**, verify:

* Bot ID = your Entra App ID.
* Personal scope is checked.

You can also add a **Personal app tab** here if you want a separate web tab next to the chat — not required for the Copilot-style chat experience.

***

### Step 6 — Install the app in Teams

#### Option A: Sideload for yourself (fastest for testing)

1. In Teams Developer Portal, click **Preview in Teams** (top right) on your app.
2. Teams opens with an install prompt → click **Add**.
3. The bot appears in your left rail. Right-click → **Pin** so it stays.

#### Option B: Deploy across your organization

This is the production path: a Microsoft 365 admin uploads the bot to your tenant's app catalog and uses **App Setup Policies** to auto-install and pin it for the right audience — either everyone in the tenant or a specific subgroup (a department, an Entra security group, a pilot team).

You will need a user with one of these roles in your tenant:

* **Teams Administrator** (or **Global Administrator**) for app upload and policy changes.
* **Groups Administrator** (or **User Administrator**) if you need to create the security/M365 group used to scope the rollout.

**B1. Submit the app via Dev Portal**

1. In Teams Developer Portal (<https://dev.teams.microsoft.com>) open your app.
2. Top right → **Distribute → Publish to your org**.
3. Confirm. The app is sent to your tenant's review queue in Teams Admin Center.

> Alternative: if you'd rather skip the review queue, click **Download app package** instead and upload the `.zip` directly via Teams Admin Center → **Manage apps → Actions → Upload new app**. End state is the same. Most users find Distribute easier because they don't have to manage a `.zip` file or remember which Teams Admin Center menu to use.

**B2. Approve and unblock the app in Teams Admin Center**

> **Heads up: Teams Admin Center has two independent statuses for every app, and you have to clear both.** This trips up almost everyone.
>
> A freshly distributed app typically lands as *Submitted* + *Blocked*. You need to publish it (B2.4) **and** unblock it (B2.5) before users can get it. An app that is *Published* but *Blocked* will look approved in the admin UI but will be invisible / un-installable for end users.

| Field                 | Values                    | Meaning                                         |
| --------------------- | ------------------------- | ----------------------------------------------- |
| **Publishing status** | *Submitted* / *Published* | Whether the app exists in your tenant's catalog |
| **Status**            | *Allowed* / *Blocked*     | Whether users are permitted to install/use it   |

1. Go to **Teams Admin Center** → <https://admin.teams.microsoft.com>.
2. Left nav → **Teams apps → Manage apps**.
3. Filter by **Publishing status: Submitted** (or look for the yellow *Pending publish* badge on your app).
4. Click your app → **Publish** button → confirm. Publishing status flips to **Published**.
5. **Unblock the app**:
   * On the app's page, find the **Status** toggle (top of the page or in the *Availability* section).
   * Set it to **Allowed**. If it's already *Allowed*, leave it.
6. **Permissions** tab → if any permissions are listed, click **Grant admin consent**.

**If unblocking the app didn't help**

There are two more places that can block a custom app even after the per-app Status is *Allowed*:

**Org-wide app settings** (tenant-level kill switch):

1. Still in **Manage apps**, top right → **Org-wide app settings**.
2. Under **Custom apps** → ensure **Let users interact with custom apps** is **On**.
3. Under **Third-party apps** → ensure custom-app interaction isn't disabled there.
4. Save.

**App permission policies** (per-user app gating):

1. **Teams apps → Permission policies**.
2. Open the policy assigned to your target user (Global / Org-wide default unless you've made custom ones).
3. Under **Custom apps**, ensure it's **Allow all apps**, **or** add your specific app to the allowed list.

> Most enterprise tenants block custom apps by default for security, so expect to flip at least the per-app Status. The org-wide and permission-policy layers are only an issue in tighter security configurations.

At this point the bot exists in the tenant catalog and is unblocked, but **nobody has it installed yet**. Distribution to users happens via App Setup Policies (next step).

**B3. Decide your audience**

Pick one before you touch policies:

| Audience                     | What you need                                         |
| ---------------------------- | ----------------------------------------------------- |
| Everyone in the tenant       | Modify the **Global (Org-wide default)** setup policy |
| A specific department / team | Create a new **custom setup policy**, then assign it  |
| A pilot group of named users | Create a new custom setup policy, then assign it      |

For department/group rollouts, **make sure the group exists in Entra ID first**:

* Entra Admin Center → **Groups → New group**.
* Group type: **Security** (cheapest) or **Microsoft 365** (if you want a shared mailbox too).
* Membership type: **Assigned** for static lists, **Dynamic User** for rule-based (e.g. `department -eq "Customer Success"`).
* Add the members. Note the **Object ID** — you'll reference this group from the Teams policy.

**B4. Create or edit the App Setup Policy**

A setup policy controls **which apps are pre-installed** and **which apps are pinned to the left rail**. You need entries in **both sections** — installing without pinning means users have to dig through "More apps" to find the bot, which kills adoption.

1. Teams Admin Center → **Teams apps → Setup policies**.
2. Either:
   * **Edit Global (Org-wide default)** → applies to every user not covered by a custom policy.
   * **Add** a new custom policy → name it (`StackAI Bot Pilot`, `Customer Success Bots`, etc.).
3. Under **Installed apps** → **Add apps**:
   * Search your app name → **Add → Add**.
   * This makes Teams silently install the app for assigned users on next launch.
4. Under **Pinned apps** → **Add apps**:
   * Search your app name → **Add → Add**.
   * Drag it to the position you want in the left rail. Top-of-list gets the most usage; below the bottom edge gets ignored.
5. **User pinning**: leave on (recommended) so users can re-pin if they accidentally remove it. Turn it off if you want to lock the rail layout.
6. **Save**.

Changes propagate to clients within \~24 hours; usually much faster (5–60 min).

**B5. Assign the policy to your audience**

Skip this step if you edited the **Global (Org-wide default)** policy — it already applies to everyone.

For a custom policy, assign it one of two ways:

**Per-user** (small lists):

1. Teams Admin Center → **Users → Manage users**.
2. Filter / select the users.
3. **Edit settings → App setup policy** → pick your custom policy → **Apply**.

**Per group** (recommended for any rollout > \~10 people):

1. Teams Admin Center → **Teams apps → Setup policies → Group policy assignment** tab.
2. **Add group assignment**.
3. Pick the Entra group from B3, choose **App setup policy**, pick your policy, set rank (lower number = higher priority if a user is in multiple groups).
4. **Apply**.

> Group assignment is rank-based: each user gets the **highest-priority** policy they're eligible for. Per-user assignment beats group assignment beats global default.

**B6. Verify the rollout**

1. Pick a sample user in the audience. Have them quit and reopen Teams (or wait up to 24h).
2. The bot should appear pinned in their left rail with no action on their part.
3. As admin, run **Teams Admin Center → Users → \[user] → Policies tab** to confirm the right setup policy is effective for that user.
4. Check **Manage apps → \[your app] → Usage** after 24h to see DAU/MAU per the rollout audience.

**B7. Shipping updates**

When you change the manifest (new icon, new bot name, new scopes):

1. In Teams Developer Portal, bump the **Version** field on the app's basic information page (semver — `1.0.0` → `1.0.1`).
2. Click **Distribute → Publish to your org** again.
3. In Teams Admin Center → **Manage apps**, find the app and approve the new version.
4. No policy changes needed — installed users get the new version automatically.

When you change **only** the workflow logic in StackAI (most common case): nothing to redeploy. The Teams app and Azure Bot are unchanged; users just get the new behavior on the next message.

**Common org-wide gotchas**

| Symptom                                                     | Cause                                                                   | Fix                                                                                                               |
| ----------------------------------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| Some users don't see the bot pinned                         | They have a **higher-priority custom policy** without your bot in it    | Add the bot to that policy too, or rank yours above it                                                            |
| Bot installed but not pinned                                | Policy has *Installed apps* entry but missing *Pinned apps* entry       | Add to *Pinned apps* in the same policy                                                                           |
| App is *Published* but users still can't install or see it  | App's **Status** is *Blocked* (default for newly published custom apps) | Manage apps → your app → toggle **Status: Allowed**                                                               |
| Per-app Status is *Allowed* but app still blocked for users | Tenant-wide custom-app block, or restrictive App Permission Policy      | Manage apps → **Org-wide app settings** → enable custom apps; check **Permission policies** for the user's policy |
| 429 / throttling under load                                 | Azure Bot still on F0 tier                                              | Upgrade Azure Bot resource to S1 before broad rollout                                                             |
| Updates not reaching users                                  | Manifest version not bumped                                             | Always increment **Version** in Dev Portal before re-distributing                                                 |
| Pilot group rolls out to everyone                           | Custom policy not assigned to a group; only Global policy was edited    | Assign custom policy via *Group policy assignment* and confirm Global policy is unchanged                         |

**Recommended rollout pattern**

1. **You only** (sideload, Option A) — verify the workflow end-to-end.
2. **5-person pilot** — custom setup policy assigned to a small Entra group of friendlies. Run for a week, gather feedback, fix prompts.
3. **Department** — assign the same policy to a larger Entra group (e.g. `Customer Success` dynamic group). Run for 2–4 weeks.
4. **Org-wide** — add the bot to the Global setup policy. Retire the pilot policy or keep it for staging changes ahead of the global rollout.

> The "feels like Copilot" effect comes entirely from B4's pinning step — without it, users have to find the app manually and adoption craters.

***

### Step 7 — End-to-end smoke test

1. In Teams, click the pinned app icon → opens a 1:1 chat panel.
2. Type "hello".
3. Watch:
   * Azure Bot → **Test in Web Chat** logs.
   * StackAI → run history for the workflow. You should see a run triggered with the message text.
4. The reply should land back in Teams within a couple of seconds.

If something is off, see troubleshooting below.

***

### Common gotchas

| Symptom                                                | Likely cause                                                                | Fix                                                                                                       |
| ------------------------------------------------------ | --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| StackAI run never triggers                             | Messaging endpoint URL wrong, or Teams channel not enabled on the Azure Bot | Re-paste the StackAI webhook URL exactly; confirm the Teams channel shows **Running**                     |
| `401 Unauthorized` on inbound                          | Tenant ID mismatch between Entra app and StackAI connection                 | For multi-tenant bots use `common` as the StackAI Tenant ID; for single-tenant use the actual tenant GUID |
| Reply action fails with `Unauthorized`                 | Client secret expired or wrong                                              | Generate a new secret in Entra → update the StackAI connection                                            |
| Bot never appears in left rail                         | Org policy doesn't allow custom apps, or app not pinned                     | Have admin enable custom app sideloading and add a pin policy                                             |
| Reply lands in a new thread instead of replying inline | Action isn't getting `reply_to_activity_id`                                 | Check the field mapping from trigger output → Reply to Message action                                     |
| Works in personal chat but not in channels             | Channel scope not enabled on manifest, or you're using the wrong trigger    | Add **Team** scope to manifest, switch trigger to **On Bot Mentioned**                                    |
| Slow first response after long idle                    | Bot Framework token cache cold start                                        | Expected; subsequent replies are fast                                                                     |

***

### Production checklist

Before rolling out to users:

* [ ] Client secret stored only in StackAI; rotation reminder set for \~22 months.
* [ ] Azure Bot pricing tier upgraded to **S1** (the free tier rate-limits hard).
* [ ] StackAI workflow has error handling that posts a graceful "something went wrong" reply on failure.
* [ ] Conversation logging / PII review done — every user message hits StackAI.
* [ ] App setup policy in Teams Admin Center pins the app for the right audience.
* [ ] Privacy and Terms URLs in the manifest point at real, reachable pages.
* [ ] If multi-tenant: a customer-facing install URL or AppSource listing is documented.

***

### Quick reference

| Thing                            | Where it lives                                           |
| -------------------------------- | -------------------------------------------------------- |
| App ID, Tenant ID, Client Secret | Microsoft Entra ID → App registrations                   |
| Messaging endpoint               | Azure Portal → Azure Bot → Configuration                 |
| Teams channel toggle             | Azure Portal → Azure Bot → Channels                      |
| StackAI trigger webhook URL      | StackAI workflow → Trigger node → "On New Teams Message" |
| Pinning policy                   | Teams Admin Center → Teams apps → Setup policies         |
| Manifest editor                  | <https://dev.teams.microsoft.com>                        |


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.stackai.com/workflow-builder/apps/teams-bot.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
