MS Teams bot DM notifications on deployment completion (Bot Framework proactive messaging)
@Otar-Khokhashvili-ext-sefe is already working on this.
Since Sep 17, 2026.
- #669 by @slafeer-sefe — open
- Dominant language
- C#
- Stars
- 5
- Forks
- 3
- Avg merge
- 1d 22h
- Merged PRs (30d)
- 14
Description
Problem
When a deployment request finishes, the only notification surface is the SignalR-driven real-time UI. A user who isn't actively watching the page has no way of knowing their job has finished. We want to push a direct 1:1 MS Teams message to the requesting user when their request reaches a terminal state.
Goals
- Deliver a Teams direct message (1:1 chat) to the user who submitted a deployment request when that request reaches a terminal state.
- Message is an Adaptive Card containing: request id, requester, project, environment, build number, final status, start/completed timestamps, duration, and a deep-link back to the DOrc UI (
/monitor-result/{id}). - Failure-tolerant: notification errors must NOT affect deployment status persistence or other event publishing.
- Configurable on/off per environment via
appsettings.json(TeamsNotificationsection) and the MSI installer parameters.
Non-goals (this issue)
- Two-way Teams interaction (approving WaitingConfirmation requests from Teams). Track separately.
- Email / Slack / generic webhook channels. The
IDeploymentNotificationSinkabstraction leaves room for them later. - Per-user opt-in/opt-out preferences UI. v1 sends to the requester unconditionally.
- Channel / Team broadcast notifications. v1 is strictly user-targeted DMs.
Delivery model: Bot Framework proactive messaging (no Graph)
⚠️ This section supersedes the original Graph-based design. The Graph chat approach (
Chat.Create/ChatMessage.Send+ MSAL) ran into problems in our tenant and was abandoned — see comment. No Graph chat permissions are required.
The Monitor sends proactive messages directly through the Bot Framework Connector:
PendingRequestProcessor / DeploymentRequestStateProcessor reach terminal status
→ IDeploymentNotificationSink.NotifyRequestCompletedAsync(request, finalStatus, startedTime, completedTime)
→ TeamsBotNotificationSink: NotifyOnStatuses filter → resolve request.UserName → Entra object id (AzureEntraSearcher)
→ ITeamsConversationClient.CreateConversationAsync (tenant + AAD object id)
→ ITeamsConversationClient.SendCardAsync (Adaptive Card 1.4)
What this design requires:
- Azure AD app registration + Azure Bot Service resource with the Microsoft Teams channel enabled — ✅ done (registrations created; currently configured for ut2 only).
- The bot app installed for the user in Teams — this is the only per-user prerequisite for receiving proactive DMs.
- Bot credentials (
BotAppId/BotAppPassword/TenantId) and the regional ConnectorServiceUrl(https://smba.trafficmanager.net/uk/) in configuration; secrets sourced from the secret store, never committed.
What this design explicitly does NOT need (all removed from the original plan):
- ❌ Microsoft Graph chat permissions (
Chat.Create,ChatMessage.Send,User.Read.Allfor chat) / MSAL client-credentials flow - ❌
TeamsUserConversationconversation-reference table inDorc.Database - ❌ Bot Framework activity endpoint (
TeamsBotController) inDorc.Api - ❌ A separate
Dorc.Notifications.Teamsproject — components live undersrc/Dorc.Monitor/Notifications/
User identity resolution
DeploymentRequestApiModel.UserNameis populated from the requester's claims (OAuthClaimsPrincipalReader): usually the email claim, falling back to SAM account name; for M2M requests it can be a machine client id.TeamsBotNotificationSinkresolvesUserName→ Entra object id via the existingAzureEntraSearcher.Search()inDorc.Core(Graph user lookup under the existingAppSettings.Aad*app credentials — distinct from the rejected Graph chat APIs).- Resolution failure (or empty
UserName) → log a warning and skip the notification; the deployment is never affected.
Implementation status — PR #669 (updated 2026-08-02)
v1 is implemented in #669 (branch feature/teams-notification-bot), including the gap-closure work below:
src/Dorc.Monitor/Notifications/—IDeploymentNotificationSink,NoOpDeploymentNotificationSink,DeploymentNotificationDispatch(fire-and-forget swallow policy),FatalExceptionssrc/Dorc.Monitor/Notifications/Teams/—TeamsBotNotificationSink(status filter, resolution, Polly retry + cooperative 10s timeout; 4xx/timeout/cancellation not retried),ITeamsConversationClient/TeamsConversationClient(Bot Connector wrapper),DeploymentCompletionCardBuilder,TeamsBotOptions- Hookpoints:
PendingRequestProcessorterminal sites andDeploymentRequestStateProcessor(cancel / abandon / stale-cleanup / errored-pickup), per-request optimistic transitions so HA monitor pairs never double-DM - DI + options in
src/Dorc.Monitor/Program.cs(bound viabuilder.Configurationso env-var/secret overrides work; malformedEnableddisables rather than crashing) - Installer wiring:
Setup.Dorc(bat, NonProd/Prod wxs, msi.json) for all 7TEAMS.*parameters - Tests: 30+ unit tests (card builder, sink, both processors' notification behaviour) in
src/Dorc.Monitor.Tests teams-app/manifest template + placeholder icons;docs/teams-notifications/teams-notifications-setup.mdsetup & rollout guide
Configuration (src/Dorc.Monitor/appsettings.json):
"TeamsNotification": {
"Enabled": "false",
"BotAppId": "",
"BotAppPassword": "",
"TenantId": "",
"ServiceUrl": "https://smba.trafficmanager.net/uk/",
"DorcUiBaseUrl": "http://localhost:8888",
"NotifyOnStatuses": "Completed,Failed,Errored"
}
Remaining work for v1 (tracked on PR #669)
- Monitor-side Entra config + DI — the
AppSettings.Aad*keys were already present in the Monitor's appsettings and installer; the searcher is now injected via DI instead of constructed inside the sink. - Tests — unit coverage for card builder, sink (disabled / filter / empty user / resolution failure / retry / swallowed failures) and both processors (fires exactly once per terminal status; faulted sink tasks never affect completion).
- Code-quality findings from PR #669 — obsolete
TrustServiceUrlremoved, conversation id null-guarded, generic catches narrowed to non-fatal filters; straysrc/src/...duplicate deleted. -
NotifyOnStatusesfilter — configurable, defaultCompleted,Failed,Errored; effective set logged at startup, unknown names warned. - Cancel/abandon path coverage —
DeploymentRequestStateProcessornow notifies on Cancelled/Abandoned/Errored transitions (per-request, duplicate-safe in HA;PendingRequestProcessordefers Cancelled to it). - Rollout — manifest template + setup docs are in the repo; still to do operationally: real icons + privacy/terms URLs in the manifest, tenant catalog upload (U3), enable environments beyond ut2.
Deferred (follow-up candidates)
- Mark
TEAMS.BOT.APP.PASSWORDHidden="yes"in the WiX property definitions so it stays out of verbose MSI logs. - Decide notification behaviour for M2M/machine requesters (currently fails safe: skip + log).
- Card "Duration" uses queue-wait for never-started requests (cancel/abandon paths) — cosmetic.
Acceptance criteria
- When a deployment request reaches a status listed in
NotifyOnStatuses, a Teams 1:1 DM (Adaptive Card with request id, requester, project, environment, build, status, timestamps, duration, deep-link) is sent to the requester within ~10 seconds of status persistence. - Notification failure of any kind (credential, network, resolution, missing bot install) does not affect deployment status updates or event publishing; failures are logged.
- A user without the bot installed simply doesn't receive the DM — logged, deployment unaffected.
-
TeamsNotification.Enabled = falseregisters the no-op sink with no side effects (unit-tested). - Unit + integration tests cover happy path, retry, swallowed-failure, and no-op-when-disabled.
- No changes to the public deployment API surface; no secret material committed. (Holds in the diff — final check at merge.)
- (Unchecked items need manual verification in ut2 once PR #669 merges and the bot is installed.)
Unknowns Register
| # | Unknown | Blocking? | Status |
|---|---|---|---|
| U1 | AAD app + Bot Service provisioning | — | ✅ Resolved — registrations created, configured for ut2 |
| U2 | Graph Chat.Create tenant policy |
— | ✅ Resolved — Graph chat approach abandoned; Bot Framework proactive messaging requires no Graph chat permissions |
| U3 | Teams app distribution — tenant-wide catalog vs sideload | Yes for go-live | Open — confirm with platform team |
| U4 | Requester identity mapping | — | ✅ Resolved — UserName is usually the email claim; resolved via AzureEntraSearcher |
| U5 | Behaviour for M2M/machine-account requesters (UserName = client id) |
No — resolution fails safe (skip + log) | Open — decide if machine requests should notify anyone |
| U6 | Adaptive Card schema version | — | ✅ Resolved — 1.4 in use |
Original ask: #385 (@benhegartysefe). Graph-based rewrite 2026-04-30; rewritten by @claude on 2026-08-02 to match the as-built Bot Framework implementation in #669 (per @slafeer-sefe's findings); checklist updated 2026-08-02 after the gap-closure work was pushed to #669.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.