@relayfile/adapter-teams
v0.3.4
Published
Microsoft Teams adapter for relayfile — maps Teams channels, messages, and change notifications to relayfile VFS paths
Readme
@relayfile/adapter-teams
Microsoft Teams adapter for relayfile. It maps Teams teams, channels, messages, replies, chats, memberships, and change notifications into deterministic VFS paths under /teams.
Quick start
- Register a Microsoft Entra app with Microsoft Graph application permissions for the scopes you need.
- Configure a public HTTPS notification endpoint that can echo
validationTokenas plain text for Graph subscription handshakes. - Create a relayfile client and instantiate
TeamsAdapter. - Create Graph subscriptions for tenant, team, channel, or chat scopes.
- Route incoming Graph notifications into
adapter.ingestWebhook(...).
import { RelayFileClient } from '@relayfile/sdk';
import { TeamsAdapter, createValidationResponse, extractValidationToken } from '@relayfile/adapter-teams';
const client = new RelayFileClient({
baseUrl: process.env.RELAYFILE_BASE_URL!,
token: process.env.RELAYFILE_TOKEN!,
});
const adapter = new TeamsAdapter(client, {
accessToken: async () => process.env.MS_GRAPH_TOKEN!,
connectionId: 'teams-primary',
clientState: process.env.TEAMS_CLIENT_STATE,
notificationUrl: 'https://example.com/api/teams/notifications',
includeResourceData: true,
encryptionCertificate: process.env.TEAMS_CERT_BASE64,
encryptionCertificateId: 'teams-cert-1',
privateKeyPem: process.env.TEAMS_PRIVATE_KEY_PEM,
});
export async function handleTeamsNotification(request: Request) {
const url = new URL(request.url);
const validation = extractValidationToken(Object.fromEntries(url.searchParams.entries()));
if (validation.isValidation) {
const response = createValidationResponse(validation.validationToken!);
return new Response(response.body, { status: response.statusCode, headers: response.headers });
}
await adapter.ingestWebhook('ws_acme', {
queryParams: Object.fromEntries(url.searchParams.entries()),
body: await request.json(),
});
return new Response(null, { status: 202 });
}Graph app registration
- Create a Microsoft Entra app registration for the adapter.
- Create a client secret or certificate for your token acquisition flow.
- Add Microsoft Graph application permissions that match the resources you will ingest:
- Channel messages and replies:
ChannelMessage.Read.All - Chat messages:
Chat.Read.All - Teams and channels metadata:
Team.ReadBasic.All - Team members:
TeamMember.Read.All
- Grant tenant-wide admin consent for the application permissions.
- If you enable
includeResourceData, configure an X.509 certificate on the Graph subscription and provide the matchingprivateKeyPemto the adapter so encrypted notification payloads can be decrypted. - Expose a public HTTPS
notificationUrland, if you want lifecycle callbacks, alifecycleNotificationUrl.
Change notification flow
Graph subscription
-> POST notificationUrl?validationToken=...
-> adapter validation helper echoes token
Graph event delivery
-> TeamsAdapter.ingestWebhook()
-> clientState verification
-> rich notification decrypt OR basic notification fetch
-> objectType + objectId normalization
-> relayfile writeFile() calls
-> VFS paths under /teamsVFS path structure
/teams/{teamId}/metadata.json
/teams/{teamId}/channels/{channelId}/metadata.json
/teams/{teamId}/channels/{channelId}/messages/{messageId}.json
/teams/{teamId}/channels/{channelId}/messages/{messageId}/replies/{replyId}.json
/teams/{teamId}/channels/{channelId}/tabs/{tabId}.json
/teams/{teamId}/channels/{channelId}/messages/{messageId}/reactions/{reactionType}--{userId}.json
/teams/{teamId}/members/{userId}.json
/teams/chats/{chatId}/metadata.json
/teams/chats/{chatId}/messages/{messageId}.jsonSubscription presets
- Tenant:
/teams/getAllMessages,/chats/getAllMessages,/teams/getAllChannels,/teams - Team:
/teams/{teamId}/channels,/teams/{teamId}/members,/teams/{teamId} - Channel:
/teams/{teamId}/channels/{channelId}/messages - Chat:
/chats/{chatId}/messages
The adapter clamps default expirations to 59 minutes and defaults to 55 minutes to stay within the short message subscription lifetime.
Use shouldRenewSubscription() to decide when to renew a subscription before Graph expires it.
Incremental sync
bulkIngestTeam() supports both full message listing and delta mode. Delta mode uses /teams/{teamId}/channels/{channelId}/messages/delta and returns channel-specific @odata.deltaLink values you can store and reuse.
Comparison with Slack adapter
- Auth: Teams uses Graph bearer tokens; Slack uses Slack app tokens.
- Event model: Teams uses Graph subscriptions and change notifications; Slack uses event callbacks.
- Message format: Teams bodies are HTML; Slack bodies are text or block structures.
- Threading: Teams replies use
replyToId; Slack usesthread_ts. - Hierarchy: Teams is
team -> channel -> message; Slack isworkspace -> channel -> message. - Files: Teams file references point to SharePoint-backed objects; Slack includes file objects directly in events.
Graph references
- Teams overview: https://learn.microsoft.com/en-us/graph/api/resources/teams-api-overview?view=graph-rest-1.0
- chatMessage resource: https://learn.microsoft.com/en-us/graph/api/resources/chatmessage?view=graph-rest-1.0
- channel resource: https://learn.microsoft.com/en-us/graph/api/resources/channel?view=graph-rest-1.0
- Teams message change notifications: https://learn.microsoft.com/en-us/graph/teams-changenotifications-chatMessage
- Change notifications overview: https://learn.microsoft.com/en-us/graph/change-notifications-overview
