Skip to main content
This page covers sending as your Microsoft Teams bot into a customer’s tenant: direct messages to people, and posts in channels. It assumes you have already created the Teams app and added the vendor. Let’s say you are building an incident product like PagerDuty. When an alert fires on Acme’s api-prod service, two things should happen in their Teams:
  • Maya, who is on call, gets a DM from your bot.
  • #incidents gets the same alert, so the rest of the team can see it.
People should also be able to @mention the bot in a channel (or open a DM with it) the way they would with a support or on-call bot. That inbound experience is your bot runtime; SuprSend sends the outbound notifications. SuprSend does not ship a Connect UI. You build admin consent, install, and the channel/user picker in your product, then write the IDs you collected. If the customer will only grant a single channel and never DMs, a webhook is enough. If they insist the bot live in their Azure directory, see Send with their bot.

Key concepts

The channel or person to notify is stored as an $ms_teams channel on a user or an object. A tenant identifies which customer account a send belongs to.

Tenants identify the customer account

Tenants in SuprSend map to accounts, organizations, or workspaces in your product. Create one tenant per customer (acme) and pass its tenant_id on every workflow trigger for that customer so branding, preferences, and vendor routing apply. The SuprSend tenant does not hold the customer’s Microsoft Entra tenant ID. SuprSend reads that from each $ms_teams entry, so keep the Entra ID in your own database against the customer account and write it into every entry you store for them.

Each $ms_teams entry names the customer’s Entra directory

Your bot is installed into many customer directories, and one SuprSend workspace serves all of them. That’s the case where tenant_id on $ms_teams matters: it tells SuprSend which directory to deliver into. If you leave it out, SuprSend falls back to the home Tenant ID saved on the vendor — your bot’s directory — and the send never reaches Acme. Include the customer’s Entra tenant ID on every entry you write for them.

Objects hold channels

A Teams channel is a connection on a resource in your product, not on a person. For the incident example, each monitored service is an object in a services type. $ms_teams on that object is #incidents (or whichever channel Acme picked for that service). When you trigger a workflow with the service as recipient, SuprSend posts to the channel stored on the object.

Users hold DMs

If Maya should get a DM, store her Teams user_id (prefer the Entra object ID) on her SuprSend user. If Maya also uses your product under Northwind, put Acme’s Teams identity on her per-tenant profile so a Northwind incident doesn’t DM her Acme chat.

Merging at send time

Trigger with the object or user as recipient and tenant_id: "acme". SuprSend uses:
  • The destination (conversation_id or user_id) on the recipient
  • The Entra tenant_id on that destination
  • The workspace vendor (your bot), unless a tenant vendor overrides it

1. Finish the app for other tenants

On Create a Teams app, keep Azure Bot App Type as Single tenant. In Azure App registrations → your app → Authentication, set Supported account types to Multiple Entra ID tenants so Acme’s admin can consent. That is not the vendor App Type.
Azure Authentication supported account types set to Multiple Entra ID tenants
Add personal and team bot scopes in the manifest if you’ll both DM and post in channels.

2. How Acme’s admin installs the app

Put this in Settings → Integrations → Microsoft Teams. An Entra / Teams admin has to click. Microsoft owns Store review. Sideload while that’s in flight: download the zip from the Developer Portal, then in Acme’s tenant Teams admin center → Teams apps → Manage apps → Upload new app. An administrator in that organization has to approve it. Until your app registration has a verified publisher (MPN ID), their end users cannot consent themselves. Installing the app in a catalog does not put it in front of anyone. For channels — in Teams, open Apps from the left rail and find your app under Added by your org or Built for your org. Open it, choose Add to a team, then pick the team that owns #incidents. Then @mention the bot once in the channel. Adding the app is not enough on its own; the mention puts the bot in the channel roster. It applies team-wide afterwards.
Teams Apps page showing the app under Added by your org
Add the org app to a team
For DMs — the app must be installed for each user in personal scope. Admins can push this to everyone through Teams admin center → Teams apps → Setup policies → Installed apps.

Automate install

Admin consent:
Then, with a token that can write the catalog in Acme’s tenant:
$TEAMS_APP_ID is the catalog id from the upload, which can differ from your Entra client id.

3. Confirm the vendor is your bot

Vendors → Microsoft Teams should already have your Application (client) ID, secret, App Type, and your home Tenant ID. That’s the bot Acme is installing. Acme’s Entra id does not go on this form. Empty Access Token + AADSTS700016 → wrong home Tenant ID. Token present but send 401 → App Type doesn’t match the Azure Bot, or a destination is missing the recipient’s tenant_id and fell back to the vendor’s directory. One MS Teams vendor serves every organization — same App ID, same password, same vendor Tenant ID (your bot’s own directory). What changes per customer is the $ms_teams.tenant_id on the user or object. Details: Add the vendor in SuprSend.

4. Store destinations

For a first test, copy IDs by hand

You don’t need a picker to prove the bot can post in Acme’s tenant.
  1. In Acme’s Teams, hover #incidentsCopy link.
  2. Decode %3A: and %40@. That’s conversation_id.
  3. The link’s tenantId= query param is Acme’s Entra id — the value you write as tenant_id on each $ms_teams entry. You can also copy it from the Azure portal under Microsoft Entra ID → Overview in their directory.
Copy link on a Teams channel to get conversation_id
Microsoft Entra ID Overview showing Directory tenant ID
For a DM, copy the personal-chat URL, or look up Maya’s Entra object ID. Prefer that object ID as user_id. Roster 29: ids are tied to this bot registration; recreate the bot and they die. Object IDs come back null on the roster unless the manifest includes RSC such as TeamMember.Read.Group.
Teams chat URL containing conversation_id
SuprSend vendor Access Token used to call the Teams roster

Channel on the service object

service_url is optional. Omit it to use https://smba.trafficmanager.net/teams, unless you’re on GCC High or DoD.

DM on the user (per-tenant profile)

That route writes Maya’s Teams identity on the Acme profile only, so a Northwind incident doesn’t use this DM. See User-tenant mapping. If you call user.add_ms_teams without a tenant, it writes the global profile instead. After install, your settings screen should list channels the app can post to, and/or DM this user. Save the channel on the service (object). Save the DM on the user (per-tenant).
Graph’s channel id is the conversation_id you store (already decoded).

5. Send a test

Create the Teams template. Trigger new-incident with tenant_id: "acme", the api-prod object, and Maya. Then trigger the same workflow for a second customer.
Acme’s #incidents and Maya’s DM show the post. Northwind’s send uses Northwind’s tenant and destinations, not Acme’s.

If something fails

Vendor App Type doesn’t match the Azure bot. A new single-tenant bot saved as Multi tenant gets a token and a 401 on send.
The app isn’t installed there, or $ms_teams.tenant_id is missing or still your directory. Write Acme’s Entra id as tenant_id on the $ms_teams entry.
App is in the tenant but not in that channel. Add it to the team and @mention the bot once.
The bot registration was recreated. Collect Entra object IDs and write those as user_id.

Next

Write the Teams template

Markdown or Adaptive Card content.

Customer brings their own bot

Same objects and users; credentials live on a tenant vendor.