Microsoft Teams Post ExecModule
Overview
TeamsPostModule creates one text or HTML chatMessage in a Microsoft Teams channel through Microsoft Graph. Version 2.0 requires a READY IntegrationAccount containing a delegated work-or-school access token, validates stable team and channel identifiers, bounds content and serialized payload size, blocks active HTML, verifies the returned message identity and Teams URL, and exposes only secret-safe failures.
Microsoft Graph permits normal channel message creation with delegated ChannelMessage.Send. Its application permission is Teamwork.Migrate.All, which Microsoft limits to migration. This module therefore does not exchange a client ID and client secret or support application-only posting.
Usage
- Register a Microsoft Entra application that supports delegated work-or-school authorization.
- Grant the least-privileged
ChannelMessage.Senddelegated permission and complete the required consent flow. - Obtain an access token for the signed-in user and store it only in a ValkyrAI IntegrationAccount
apiKey. - Mark the account
READY, bind it throughExecModuleConfig.authConfig.integrationAccount, and select the stable team and channel identifiers. - Supply
contentand choosetextorhtml. - Route the outbound message through any required human-approval policy, then execute once.
Use this module only for messages people are expected to read. Microsoft explicitly prohibits treating Teams as a log sink.
Inputs
| Name | Type | Required | Description | Local constraints |
|---|---|---|---|---|
team_id | string | Yes | Stable Microsoft Teams team identifier. | UUID-shaped value; display names are rejected. Legacy teamId is accepted. |
channel_id | string | Yes | Stable Teams channel identifier. | 1–256 characters from the bounded Graph identifier character set. Legacy channelId is accepted. |
content | string | Yes | Text or HTML message body. | 1–20,000 Unicode code points. Legacy text and html are accepted. |
content_type | string | No | Graph body type. | text or html; defaults to text. Legacy contentType is accepted. |
The complete serialized request is limited to 28,000 UTF-8 bytes. mock is rejected and cannot fabricate delivery. HTML rejects active elements, event-handler attributes, and javascript: or data: URLs; Microsoft Graph applies final markup validation.
Outputs
| Name | Type | When present | Description |
|---|---|---|---|
teams.message.id | string | Success | Microsoft Graph message identifier returned by the create operation. |
teams.message.web_url | string | Success | HTTPS teams.microsoft.com URL returned for the created message. |
teams.message.status | string | Always | SENT after a validated 201 response or ERROR on failure. |
The runtime ExecModule is also marked GOOD or ERROR. EventLogs contain bounded progress and sanitized failure details; access tokens, message bodies, and provider response bodies are not written to logs or errors.
IntegrationAccount Requirements
| Setting | Requirement |
|---|---|
| Provider | Microsoft Graph / Microsoft Teams |
| Authentication | Delegated work-or-school OAuth access token |
| Status | READY |
accountName | Human-readable tenant and user identity |
apiKey | Encrypted delegated access token |
password | Unused; client-secret exchange is intentionally unsupported |
| Relationship | Bind through ExecModuleConfig.authConfig.integrationAccount |
The token needs delegated ChannelMessage.Send, the user must be authorized for the target team and channel, and tenant policy can impose additional consent or conditional-access requirements. Never put the token in content, URLs, payload parameters, logs, documentation examples, or error text.
Configuration
Illustrative normalized configuration:
{
"version": "2.0.0",
"authConfig": {
"authStrategy": 1,
"integrationAccount": "integration-account:teams-release-user"
},
"payloadConfig": {
"parameters": "{\"team_id\":\"12345678-1234-1234-1234-1234567890ab\",\"channel_id\":\"19:release-updates@thread.tacv2\",\"content\":\"Release verified.\",\"content_type\":\"text\"}"
}
}
The account value is a symbolic secure reference. Persisted workflows use the generated IntegrationAccount relationship, never a plaintext token. The default transport has a 10-second connection timeout and a 30-second read timeout.
Operations
TeamsPostModule performs exactly one operation:
POST /v1.0/teams/{team-id}/channels/{channel-id}/messages
| Shape | Provider payload | Side effect |
|---|---|---|
| Text | body.contentType=text and bounded content | Creates one channel message. |
| HTML | body.contentType=html and bounded, locally screened markup | Creates one formatted channel message. |
| Legacy text | text without content | Normalizes to a text message. |
| Legacy HTML | html without content | Normalizes to an HTML message. |
Replies, chats, mentions, hosted content, attachments, reactions, updates, deletes, imports, bulk sends, webhook cards, and application-only migration are separate operations and are not implemented here.
Errors and Failure Modes
| Failure | Cause | Retry guidance |
|---|---|---|
| Validation failure | Missing or malformed IDs, missing content, unsupported content type, active HTML, content overflow, or payload overflow. | Correct the configuration; no Graph request was sent. |
| IntegrationAccount failure | No bound account, non-READY status, or blank delegated token. | Bind or reauthorize the delegated account; no Graph request was sent. |
| HTTP 401/403 | Expired token, missing delegated scope, channel access denial, consent policy, or application-only token. | Reauthorize the account with delegated ChannelMessage.Send and verify target access. |
| HTTP 429 | Microsoft Graph throttling. | Reconcile whether the message exists, honor provider guidance, then schedule a deliberate new attempt. |
| HTTP 5xx or network failure | Provider or transport failure with an ambiguous commit boundary. | Do not retry blindly; inspect the target channel first. |
| Invalid success response | The response is not 201 JSON, lacks a valid ID or Teams URL, or identifies a different team/channel. | Treat as failure and inspect provider compatibility; success is not recorded. |
Provider bodies can contain tenant data or credential-like details. HTTP failures expose only the sanitized status. Errors are rethrown without retaining the provider exception as a cause.
Example
Create a plain-text release notification:
{
"team_id": "12345678-1234-1234-1234-1234567890ab",
"channel_id": "19:release-updates@thread.tacv2",
"content": "Release verified. Production route checks passed.",
"content_type": "text"
}
Expected result shape after Microsoft Graph returns a verified message:
{
"teams.message.id": "1723046500654",
"teams.message.web_url": "https://teams.microsoft.com/l/message/19:release-updates@thread.tacv2/1723046500654",
"teams.message.status": "SENT"
}
Notes
- Pagination: not applicable; one execution attempts one message creation.
- Limits: local validation enforces 20,000 content code points and a 28,000-byte JSON body. Microsoft Graph and Teams enforce final service, tenant, and markup limits.
- Idempotency: channel message creation is non-idempotent. A workflow retry can create a duplicate, and WorkflowState is not treated as provider deduplication across ambiguous failures.
- Rate limits: HTTP 429 is surfaced without retry. The module does not retain rate-limit headers or provider response bodies.
- API constraints: the normal create-message flow requires delegated work-or-school authorization. Application permission is reserved for migration and is outside this module.
- HTML: only passive bounded markup is accepted locally. Active elements, inline event handlers, and executable URL schemes fail before transport.
- Destructive behavior: creation is an external outbound write. This module cannot delete or compensate a message; removal requires a separately authorized operation.
- Observability: EventLogs record validation, submission, message ID, or sanitized failure. Message content, tokens, and provider bodies are excluded from failure logs.
- Unverified boundary: deterministic tests cover IntegrationAccount binding, exact request construction, legacy aliases, bounds, HTML safety, response mapping, non-retry behavior, metadata serialization, and secret redaction. Live Microsoft Graph execution requires separately authorized delegated credentials and is not exercised in repository tests.
See Microsoft's official channel message create API, Microsoft Graph permissions reference, and authorization code flow for current provider behavior.