Skip to main content

Installation

npm install @chat-adapter/teams

Environment Variables

Register your bot at Azure Bot Service.
VariableRequiredDescription
TEAMS_APP_IDYesMicrosoft App ID from Azure AD
TEAMS_APP_PASSWORDYesMicrosoft App Password (client secret)
TEAMS_APP_TENANT_IDSingleTenant onlyAzure AD Tenant ID

Configuration Options

interface TeamsAdapterConfig {
  /** Microsoft App ID */
  appId: string;
  /** Microsoft App Password */
  appPassword: string;
  /** Microsoft App Tenant ID (required for SingleTenant) */
  appTenantId?: string;
  /** App Type: 'MultiTenant' (default) or 'SingleTenant' */
  appType?: 'MultiTenant' | 'SingleTenant';
  /** Logger instance */
  logger: Logger;
  /** Override bot username (optional) */
  userName?: string;
}

Setup

For apps available to any organization:
import { Chat } from 'chat';
import { createTeamsAdapter } from '@chat-adapter/teams';
import { MemoryState } from '@chat-adapter/state-memory';

const chat = new Chat({
  userName: 'my-bot',
  adapters: {
    teams: createTeamsAdapter({
      appId: process.env.TEAMS_APP_ID!,
      appPassword: process.env.TEAMS_APP_PASSWORD!,
      // appType defaults to 'MultiTenant'
    }),
  },
  state: new MemoryState(),
});

await chat.initialize();

Webhook Handler

app.post('/webhooks/teams', async (req, res) => {
  const response = await teams.handleWebhook(req, {
    waitUntil: (promise) => {/* handle async work */},
  });
  res.status(response.status).send(await response.text());
});

Features

Supported Events

  • Message - Regular messages in channels/chats
  • MessageReaction - Emoji reactions added/removed
  • Invoke (Adaptive Card actions) - Button clicks
  • Action.Submit - Form submissions from Adaptive Cards

Message Types

The adapter handles:
  • Channel messages - Public/private channel posts
  • 1:1 chats - Direct messages with users
  • Group chats - Multi-user conversations
  • Thread replies - Replies in message threads

Adaptive Cards

Teams uses Adaptive Cards for rich UI:
import { Card, Section, Button } from 'chat/cards';

await thread.post(
  <Card title="Approval Request">
    <Section text="Deploy v2.0 to production?" />
    <Button actionId="approve" style="primary">Approve</Button>
    <Button actionId="deny" style="danger">Deny</Button>
  </Card>
);
Cards are automatically converted to Adaptive Card format. Text messages support basic markdown.

Reactions

Teams Bot Framework does not expose APIs for adding/removing reactions programmatically. The adapter can receive reaction events but cannot create them.

File Attachments

Send files via inline data URIs:
import { readFileSync } from 'fs';

await thread.post({
  text: 'Quarterly report',
  files: [{
    filename: 'report.xlsx',
    data: readFileSync('./report.xlsx'),
    mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  }],
});
Files are sent as data URIs. For large files, consider uploading to external storage and sharing links.

Thread IDs

Teams thread IDs encode conversation ID and service URL:
teams:{base64(conversationId)}:{base64(serviceUrl)}:{base64(replyToId)}
The service URL varies by Teams cloud (commercial, GCC, GCC-High).

Opening DMs

Create a 1:1 conversation with a user:
const dmThreadId = await teams.openDM(userId);
await chat.getThread(dmThreadId).post('Hello!');
The user must have interacted with the bot first (via @mention) to cache their tenantId and serviceUrl. Without cached values, openDM() will throw a validation error.

Message History

fetchMessages() requires Microsoft Graph API access with one of these permissions:
  • ChatMessage.Read.Chat
  • Chat.Read.All
  • Chat.Read.WhereInstalled
Configure appTenantId to enable Graph API access.
Fetch message history from a thread:
const { messages, nextCursor } = await thread.fetchMessages({
  limit: 50,
  direction: 'backward', // or 'forward'
});

Platform Limits

  • Message length: 28 KB (HTML content)
  • Adaptive Card size: 28 KB total
  • File size: 4 MB via inline data URI
  • Rate limits: Teams throttling thresholds

Thread Context Caching

The adapter automatically caches:
  • User service URLs (for DM creation)
  • Tenant IDs (for Graph API calls)
  • Team GUID mappings (for channel message fetching)
Cache entries expire after 30 days and are stored in the StateAdapter.

Code Examples

chat.onNewMessage(async (event) => {
  if (event.message.text.includes('help')) {
    await event.thread.post('Available commands: /deploy, /status');
  }
});

Troubleshooting

  • Verify TEAMS_APP_ID and TEAMS_APP_PASSWORD are correct
  • Check that the messaging endpoint in Azure matches your webhook URL
  • Ensure the webhook URL is publicly accessible (HTTPS required)
  • Install the app to the team via Teams admin center or app store
  • @mention the bot first to establish context
  • Enable “Receive messages in channels” in app manifest
  • Add appTenantId to adapter config (required for Graph API)
  • Grant Graph API permissions in Azure AD
  • Wait for admin consent if required by your tenant
  • User must interact with bot first (via @mention) to cache tenantId
  • For SingleTenant apps, provide appTenantId in config

App Manifest

Your Teams app manifest (manifest.json) should include:
{
  "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.16/MicrosoftTeams.schema.json",
  "manifestVersion": "1.16",
  "id": "YOUR_APP_ID",
  "version": "1.0.0",
  "bots": [
    {
      "botId": "YOUR_APP_ID",
      "scopes": ["personal", "team", "groupchat"],
      "supportsFiles": true,
      "isNotificationOnly": false
    }
  ],
  "permissions": ["identity", "messageTeamMembers"],
  "validDomains": ["your-domain.com"]
}
See Teams manifest schema for full reference.

Next Steps

Message Handling

Process messages and build conversation flows

Adaptive Cards

Create interactive UIs with Adaptive Cards

Bot Framework

Learn more about Azure Bot Service

State Management

Persist data across requests