Installation
npm install @chat-adapter/teams
Environment Variables
Variable Required Description TEAMS_APP_IDYes Microsoft App ID from Azure AD TEAMS_APP_PASSWORDYes Microsoft App Password (client secret) TEAMS_APP_TENANT_IDSingleTenant only Azure 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
Multi-Tenant (Default)
Single-Tenant
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 ();
For internal apps restricted to your 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 ! ,
appTenantId: process . env . TEAMS_APP_TENANT_ID ! ,
appType: 'SingleTenant' ,
}),
},
state: new MemoryState (),
});
await chat . initialize ();
SingleTenant apps require appTenantId or the adapter will throw a validation error.
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'
});
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
Message Handler
Reaction Handler
Action Handler
Adaptive Card
chat . onNewMessage ( async ( event ) => {
if ( event . message . text . includes ( 'help' )) {
await event . thread . post ( 'Available commands: /deploy, /status' );
}
});
Troubleshooting
Webhook returns 401 Unauthorized
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)
Bot doesn't respond in channels
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
fetchMessages() not working
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
openDM() throws validation error
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