Microsoft Teams integration

If you're using Teams you can use the CommerceMind.Nexus.Teams NuGet package to get realtime updates when jobs, queues, and health checks are failing. The package contains an IHealthCheckPublisher implementation that publishes health check failures to Teams through a Teams bot.

Teams webhook connectors have been retired by Microsoft. Nexus still contains the old AddTeamsPublisher webhook API for a migration window, but new installations should use direct Teams bot mode or outgoing-only bridge mode.

Register a Teams bot

If you haven't already you need to register a Teams bot. This guide helps you with the steps:

https://learn.microsoft.com/en-us/microsoftteams/platform/teams-sdk/get-started/quickstart-register?pivots=csharp

Note that when registering your bot you will need a public url into Nexus that Teams can communicate with. So read the rest of the docs first before registering your bot if you're unsure about the url.

The guide helps you register a Teams bot in your Teams workspace and will create a Teams section in appSettings.json with ClientId, ClientSecret, TenantId which you'll need regardless if you use the bridge mode or direct mode. If you use the direct mode you'll add these to the appSettings.json of your Nexus application. If you use the bridge mode you'll add them to the appSettings.json file of your bridge app instance.

Note that regardless if you use direct mode or bridge mode Teams needs a public url that it can ping you on. In direct mode that's directly to your Nexus instance and in bridge mode it's a separate, public facing web application that acts as a bridge between your Nexus application and Teams.

Notifications

The Teams bot publisher stores the conversation where the bot is installed and sends proactive Adaptive Card messages to that chat or channel. It can update the original card when a failing check changes, when someone clicks a bot-side button, and when the check becomes healthy again.

Supported actions include:

  • Failing scheduled job: show or reload error logs, and open the job in the Nexus UI.
  • Long-running scheduled job: show or reload logs, and open the job in the Nexus UI.
  • Queue errors: show or reload the latest 5 errors, and open the queue in the Nexus UI.
  • Generic health check: show the health check status and open the health checks page in the Nexus UI.

Nexus keeps the active Teams message IDs in its key-value store, so cards can still be updated after the application restarts. In multi-instance Nexus deployments, only one Nexus instance publishes Teams health messages at a time.

Direct mode

Use direct mode when the Nexus application that publishes health checks is reachable by Teams over public HTTPS.

Install the NuGet package CommerceMind.Nexus.Teams and configure the Teams bot publisher in Program.cs:

Configure the Teams bot publisher in Program.cs:

builder.Services
    .AddNexus()
    .AddTeamsBotPublisher(options =>
    {
        // A required secret that protects linking a Teams conversation to notifications. Use a long random value.
        options.LinkCode = builder.Configuration["NexusTeamsBot:LinkCode"]!;

        // This is optional and used to add link buttons to the messages to open the Nexus UI.
        options.AdminUIUrl = "https://url-to-nexus-adminui.com";

        // The default is Degraded. Set to Unhealthy to skip degraded health checks.
        options.MinimumHealthStatus = HealthStatus.Degraded;
    });

var app = builder.Build();

app.UseNexusTeamsBot();

Teams commonly uses /api/messages in Bot Framework examples, but the path itself is not a Teams requirement. UseNexusTeamsBot() registers the Teams bot endpoint at /nexus-teams/bot/messages by default but if you need another route, pass it explicitly:

app.UseNexusTeamsBot("api/messages");

Create and configure a Teams app/bot using the standard Microsoft Teams SDK configuration. The application that runs Nexus must be reachable by Teams, and the bot messaging endpoint should point to your Nexus application, for example:

https://your-nexus-host.example.com/nexus-teams/bot/messages

If AdminUIUrl is configured, add that host to the Teams app manifest validDomains list. Teams requires this for Action.OpenUrl buttons in Adaptive Cards.

Linking a conversation requires the configured LinkCode, so that being able to install or message the bot is not by itself enough to subscribe a conversation to health alerts.

Installing the bot does not auto-link the conversation; send @name-of-bot link {linkCode} to link the current Teams conversation, and send @name-of-bot unlink from the same conversation to remove it. Multiple chats or channels can be linked, and health notifications are sent to every linked conversation. Keep the link code out of shared channels where possible, and remove the linking message after the link has been created.

Outgoing-only bridge mode

Use bridge mode when Nexus is hosted as an internal application that Teams cannot reach over the internet. The public bridge receives Teams traffic, while the internal Nexus application only makes outbound HTTPS requests to the bridge.

Install CommerceMind.Nexus.Teams in the internal Nexus application and configure bridge publishing:

builder.Services
    .AddNexus()
    .AddTeamsBridgePublisher(options =>
    {
        options.BridgeUrl = new Uri("https://teams-bridge.example.com");
        options.NexusAppId = "erp-prod";
        options.ApiKey = builder.Configuration["NexusTeamsBridge:ApiKey"]!;
    });

The internal Nexus application does not need Teams bot credentials and does not need to call UseNexusTeamsBot().

Host a public bridge application with CommerceMind.Nexus.Teams.Bridge:

// Program.cs
builder.Services.AddNexusTeamsBridge();

builder.Services.AddNexusTeamsBridgeSqlServer(builder.Configuration.GetConnectionString("NexusTeamsBridge")!);
// or AddNexusTeamsBridgePostgres(...)
// or AddNexusTeamsBridgeSqlite(...)

var app = builder.Build();

app.UseNexusTeamsBridge();

The bridge is just a simple ASP.NET Core web application with at least the CommerceMind.Nexus.Teams.Bridge package installed that you need to deploy to somewhere that Teams can access. It needs a stable IP or domain that you can use when you register your teams bot.

The bridge needs a database to maintain state between restarts of the bridge application. It uses Entity Framework internally. You can point it to an existing database, but you should not point it to a database already owned by EF.

The bridge registers the Teams bot endpoint at /nexus-teams/bot/messages. Configure the Teams app/bot messaging endpoint to point to the public bridge:

https://teams-bridge.example.com/nexus-teams/bot/messages

A single bridge can serve many Nexus applications. The bridge application is designed to run as one active server, with support for short blue-green deployment overlap.

Bridge database schema

The bridge uses EF Core migrations for its own tables. AddNexusTeamsBridge() runs Database.MigrateAsync() at startup by default, so a new bridge database is created automatically when the configured database user has schema permissions.

Set AutoMigrateDatabase = false when migrations are applied by a deployment pipeline, migration bundle, or another operational process.

Bridge app registration

Nexus app registrations are managed directly in the bridge database. There is intentionally no bridge admin HTTP API.

Insert one row per Nexus application:

INSERT INTO NexusTeamsBridgeApps
(
    NexusAppId,
    DisplayName,
    AdminUIUrl,
    ApiKey,
    LinkCode,
    IsEnabled,
    CreatedAtUtc,
    UpdatedAtUtc
)
VALUES
(
    'erp-prod',
    'ERP Production',
    'https://internal-erp-nexus/admin',
    'generated-long-random-secret',
    'generated-long-random-link-code',
    1,
    CURRENT_TIMESTAMP,
    CURRENT_TIMESTAMP
);

API keys and link codes are stored in plain text in the bridge database. Protect the bridge database accordingly, use long random values, and rotate them by directly updating the ApiKey and LinkCode columns.

The ApiKey authenticates the internal Nexus application to the bridge (server-to-server). The LinkCode is a separate secret that protects who may subscribe a Teams conversation to an app's notifications: because the NexusAppId is not secret and is easy to guess, linking additionally requires the link code so that being able to message the bot is not enough to receive an app's health alerts. Keep the link code out of shared channels where possible; because it is typed into a Teams message it is visible in that conversation's history, so rotate it if it is exposed. Delete the link message after the link is created.

The default Nexus-to-bridge authentication uses the X-Nexus-Teams-Bridge-Api-Key HTTP header. This keeps bridge API credentials separate from the Teams SDK's own Authorization handling. Internal Nexus applications can replace request signing by registering INexusTeamsBridgeRequestSigner before calling AddTeamsBridgePublisher(). Bridge hosts can replace request authentication by registering INexusTeamsBridgeRequestAuthenticator before calling AddNexusTeamsBridge().

Link a Teams conversation to a registered Nexus app by sending one of these messages to the bridge bot, using the app's configured link code:

@name-of-bot link erp-prod {linkCode}

The link code must match the LinkCode stored for that app or the link is refused. Running the same command from another chat or channel adds that conversation to the app's notification destinations; the bridge sends each health notification to every linked conversation for that Nexus app.

Remove the current Teams conversation from a Nexus app with:

@name-of-bot unlink erp-prod