Skip to main content

Setup

This guide walks through everything needed to connect a Microsoft Teams bot to OctoMesh, step by step — first in the Azure Portal, then the equivalent Azure CLI (az) commands, and finally the OctoMesh side (configuration, pipeline, Teams app package, test).

Prerequisites​

  • An Azure subscription and permission to create an App registration and an Azure Bot resource.
  • A Microsoft Teams account with permission to sideload a custom app (if greyed out, a Teams admin must enable Upload custom apps in the app setup policy).
  • A running OctoMesh mesh adapter for the target tenant (the bot's messaging endpoint points at it).
  • For local development: a tunnelling tool such as dev tunnels or ngrok to expose the local adapter over HTTPS.
Naming used below

The examples use the resource group my-rg, the bot handle octo-accounting-bot, and the tenant id salzburgdev. Replace them with your own values.


Step 1 — App registration + client secret​

The App registration is the bot's identity. Its client ID/secret are used both for the inbound authentication and for TeamsBotReply@1's outbound token.

Portal​

  1. Microsoft Entra ID → App registrations → + New registration.
  2. Name: octo-accounting-bot.
  3. Supported account types: Accounts in this organizational directory only (Single tenant).
  4. Register, then note Application (client) ID and Directory (tenant) ID.
  5. Certificates & secrets → + New client secret → set an expiry → Add → copy the Value immediately (it is shown only once).

Azure CLI​

# create a single-tenant app registration
az ad app create --display-name "octo-accounting-bot" --sign-in-audience AzureADMyOrg
# -> note the "appId" from the output

# add a client secret (prints the secret once)
az ad app credential reset --id <APP_ID> --append --display-name teams-bot --years 1 \
--query password -o tsv

# your tenant id
az account show --query tenantId -o tsv

Keep three values for later: App (client) ID, client secret, tenant ID.


Step 2 — Azure Bot resource​

Portal​

  1. Create a resource → search "Azure Bot" → Create.

  2. Bot handle: octo-accounting-bot.

  3. Choose your Subscription and Resource group.

  4. Pricing tier: Change plan → F0 (Free) is sufficient for most scenarios.

  5. Microsoft App ID:

    • Type of App: Single Tenant.
    • Creation type: Use existing app registration → paste the App ID from Step 1.
    Do not pick "User-Assigned Managed Identity"

    A Managed Identity bot has no client secret and cannot authenticate the self-hosted mesh adapter. Use Single Tenant with the app registration from Step 1.

  6. Review + Create → Create.

  7. On the bot resource, open Channels → Microsoft Teams, accept the terms and Apply.

  8. On Configuration, set the Messaging endpoint (see Step 3 for the URL) and Apply.

Azure CLI​

RG=my-rg
BOT=octo-accounting-bot
APP_ID=<APP_ID> # from Step 1
TENANT_ID=<TENANT_ID> # from Step 1
ENDPOINT="https://<public-host>/salzburgdev/teamsBot" # from Step 3

# create the bot (single tenant, free tier)
az bot create -g $RG -n $BOT --app-type SingleTenant --appid $APP_ID \
--tenant-id $TENANT_ID --endpoint "$ENDPOINT" --sku F0

# enable the Microsoft Teams channel
az bot msteams create -n $BOT -g $RG

# (later) update just the messaging endpoint, e.g. after the tunnel URL changes
az bot update -n $BOT -g $RG --endpoint "$ENDPOINT"

# verify
az bot show -n $BOT -g $RG \
--query "{appId:properties.msaAppId, appType:properties.msaAppType, endpoint:properties.endpoint}" -o json
az bot msteams show -n $BOT -g $RG --query "properties.properties.isEnabled" -o tsv
Verify both the endpoint AND the Teams channel

A missing messaging endpoint or a disabled Teams channel both surface in Teams as "Invalid bot". The two show commands above confirm the endpoint is set and isEnabled is true.


Step 3 — Expose the messaging endpoint​

The messaging endpoint is https://<public-host>/{tenant}/teamsBot, where {tenant} is the adapter's tenant id (for example salzburgdev) and <public-host> reaches the mesh adapter.

Local development​

Expose the adapter's HTTP listener through a tunnel (avoids the self-signed-TLS hop):

# dev tunnels
devtunnel host -p 5041 --allow-anonymous

# or ngrok
ngrok http 5041

Take the tunnel's public HTTPS URL and set the messaging endpoint to https://<tunnel-host>/salzburgdev/teamsBot. With ngrok's free plan the URL changes on each restart — reserve a static domain or re-run az bot update --endpoint … after each start.

Production​

Point the messaging endpoint at the adapter's public HTTPS address (for example behind an ingress / reverse proxy), keeping the /{tenant}/teamsBot path.


Step 4 — Configure OctoMesh​

1. Bot credentials (MicrosoftGraphConfiguration)​

Provide the App ID / secret / tenant id as a System.Communication/MicrosoftGraphConfiguration entity. Store the secret outside of source control (a local secrets file, a key vault, …).

- rtId: <config-rtId>
ckTypeId: System.Communication/MicrosoftGraphConfiguration
rtWellKnownName: MicrosoftGraphDocuments
attributes:
- id: System.Communication/AzureTenantId
value: "<tenant id>" # REQUIRED for a single-tenant bot
- id: System.Communication/ClientId
value: "<app (client) id>"
- id: System.Communication/ClientSecret
value: "<client secret>"

2. Pipeline with FromTeamsBot@1 and TeamsBotReply@1​

The pipeline must carry a System.Communication/Uses association to the MicrosoftGraphConfiguration (the GlobalConfiguration is built only from Uses associations — the name alone is not resolved).

# on the Pipeline entity
associations:
- roleId: System.Communication/Uses
targetRtId: <config-rtId>
targetCkTypeId: System.Communication/MicrosoftGraphConfiguration

A minimal echo pipeline definition:

triggers:
- type: FromTeamsBot@1
serverConfiguration: MicrosoftGraphDocuments
route: /teamsBot
validateInboundToken: false # local dev; enable + harden before public exposure
transformations:
- type: TeamsBotReply@1
serverConfiguration: MicrosoftGraphDocuments
messageBody: "Hello from OctoMesh!"
# serviceUrlPath / conversationIdPath default to $.Conversation.ServiceUrl / .ConversationId

A real pipeline branches on $.Emails[0].Attachments: attachments → ingest the file(s), plain text → answer (e.g. with AnthropicAiQuery@1), then reply through TeamsBotReply@1 reading $.Conversation.

3. Deploy​

Import the configuration and pipeline and deploy the data flow with the octo-cli (ImportRt, DeployDataFlow). After deployment the adapter logs FromTeamsBot: listening on /teamsBot.


Step 5 — Teams app package​

A bot is reached in Teams through a small app package (a zip containing manifest.json and two icons). The manifest's bots[].botId must be the bot's App ID, and supportsFiles: true is required so the bot can receive file uploads in 1:1 chats.

manifest.json
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.17/MicrosoftTeams.schema.json",
"manifestVersion": "1.17",
"version": "1.0.0",
"id": "<app (client) id>",
"developer": {
"name": "Your Company",
"websiteUrl": "https://example.com",
"privacyUrl": "https://example.com/privacy",
"termsOfUseUrl": "https://example.com/terms"
},
"icons": { "color": "color.png", "outline": "outline.png" },
"name": { "short": "Accounting Assistant", "full": "OctoMesh Accounting Assistant" },
"description": { "short": "Upload invoices and ask questions.", "full": "…" },
"accentColor": "#0076D7",
"bots": [
{ "botId": "<app (client) id>", "scopes": ["personal"], "supportsFiles": true }
],
"permissions": ["identity", "messageTeamMembers"],
"validDomains": []
}

Provide color.png (192×192) and outline.png (32×32, transparent), zip the three files at the root of the archive, then in Teams: Apps → Manage your apps → Upload an app → Upload a custom app and select the zip.


Step 6 — Test​

  1. Open the bot from Teams (the Add dialog after sideloading, or search its app name).
  2. Send a text message → the pipeline runs and TeamsBotReply@1 answers in the chat.
  3. Attach a file (e.g. a PDF) → the bot ingests it ($.Emails[0].Attachments[0].Data) and replies.

Watch the adapter log to confirm the round-trip:

FromTeamsBot: listening on /teamsBot
FromTeamsBot: processed activity from <user> with <n> attachment(s)

Node reference​

FromTeamsBot@1 (trigger)​

PropertyDescription
serverConfigurationWellKnownName of the MicrosoftGraphConfiguration (bot App ID/secret + tenant).
routeRelative route of the messaging endpoint; the tenant prefix is added by the adapter. Default /teamsBot.
validateInboundTokenValidate the inbound Bot Framework JWT. Default false (local dev). See Security below.
botAppIdExpected token audience; defaults to the configuration's ClientId.

Output: $.Emails[] (message + attachments, same shape as the e-mail triggers) and $.Conversation (ServiceUrl, ConversationId, ActivityId, FromId, FromName, FromAadObjectId).

TeamsBotReply@1 (load)​

PropertyDescription
serverConfigurationWellKnownName of the MicrosoftGraphConfiguration.
serviceUrlPathJSONPath to the Bot Framework serviceUrl. Default $.Conversation.ServiceUrl.
conversationIdPathJSONPath to the conversation id. Default $.Conversation.ConversationId.
replyToActivityIdPathOptional JSONPath for a threaded reply. Default $.Conversation.ActivityId.
messageBodyPathJSONPath to the reply text (e.g. an AI answer).
messageBodyLiteral reply text (used when messageBodyPath is blank).
timeoutSecondsHTTP timeout. Default 30.
continueOnErrorContinue the pipeline if sending fails. Default true.

Security​

validateInboundToken controls inbound authentication:

  • false (default) — no authentication. Acceptable only for local development behind a private dev tunnel or the Bot Framework Emulator.
  • true — the inbound Bot Framework JWT is checked (audience + expiry).
Harden before public exposure

The current inbound check validates the token's audience and expiry but not yet its cryptographic signature. Before exposing the endpoint publicly (test/production), place it behind full Bot Framework token validation and reject unauthenticated requests. Until then any caller who knows the URL can trigger the pipeline.


Troubleshooting​

SymptomCause / fix
"Invalid bot" / "Ungültiger Bot" in TeamsMessaging endpoint empty or Teams channel not enabled. Verify with az bot show … --query properties.endpoint and az bot msteams show … --query properties.properties.isEnabled; fix with az bot update --endpoint … and az bot msteams create.
Bot Framework POSTs arrive (200) but no pipeline runsThe first activities on opening a chat are conversationUpdate/typing events, which the trigger ignores by design. Only message activities run the pipeline.
invalid_client / no reply is deliveredWrong token authority. For a single-tenant bot, AzureTenantId must be set on the MicrosoftGraphConfiguration (the reply node then uses the tenant authority).
Files are not received in a 1:1 chatThe Teams app manifest is missing "supportsFiles": true in the bots entry.
Reply fails with a DNS/socket error to the service URLExpected when testing with a fake serviceUrl; a real Teams conversation provides a reachable serviceUrl.
No FromTeamsBot: listening on /teamsBot in the logThe data flow is not deployed, or the adapter build predates the node — redeploy and restart the adapter.