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 planF0 (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.