Skip to main content
The question delivery service is the server side of dotbot’s human-in-the-loop flow. When a task reaches needs-input, dotbot sends the question to this service, which delivers it to a person over Teams, email, Jira, or Slack and collects the answer. The service is an ASP.NET Core application built on the Microsoft 365 Agents SDK, targeting .NET 9, and is designed to run in Azure App Service. The client refers to it as the “mothership”. This page covers running and deploying the service. For the client side, the mothership settings block and how dotbot routes a question, see Teams integration.

How it fits the needs-input flow

When the AI cannot proceed without a decision, it calls the task_set_status MCP tool to move the task to needs-input and records the question on the task. dotbot’s notification client reads the mothership settings block and posts the question to the service. The service delivers it on the configured channel, waits for the person to answer, and stores the answer. dotbot polls for the answer on its poll_interval_seconds interval, attaches it to the paused task, and resumes the pipeline.

Channels

The service delivers to any enabled channel. It exposes POST /api/notify and routes the question per channel:
ChannelDeliveryAnswer path
TeamsAdaptive CardThe person taps a choice; the answer returns through the Bot Service to /api/messages.
EmailMicrosoft Graph sendMail (HTML)The message includes a magic-link URL to the /respond page.
JiraREST comment on the issueThe comment includes a magic-link URL to the /respond page.
Slackchat.postMessage (Block Kit)The message includes a magic-link URL to the /respond page.
For email, Jira, and Slack, the recipient clicks the magic link and answers on the /respond Razor page. For Teams, the recipient answers directly in the card. Either way, the answer is persisted to Azure Blob Storage.

Question types

The service supports five question types:
TypeWhat the person does
singleChoicePicks one option.
multiChoicePicks one or more options.
approvalApproves or rejects a proposal. This type also carries attached documents for review, absorbing what was previously a separate document review type.
freeTextWrites a free-form answer.
priorityRankingOrders a set of items by priority.

Where answers are stored

Each answer is persisted as a flat JSON record in Azure Blob Storage, in the answers container. The record holds the answer and responder fields at the top level, and it deliberately does not duplicate the question or the recipient list. When dotbot reads the answers, the service assembles each stored record into the full response envelope it returns over the API.

Running locally

Prerequisites for local development:
  • .NET 9 SDK
  • Azure CLI
  • Terraform 1.6 or later
  • An Azure subscription
Run the service from the repository:
dotnet run --project src/Dotbot.Server
The service listens on http://localhost:5048. Teams, Bot Service, and the magic-link channels need a public endpoint, so expose the local port with a dev tunnel or ngrok, then point the Bot Service messaging endpoint at the tunnel URL.

Required configuration

The service reads its configuration from appsettings files and user secrets. Several keys are required at startup, and the service fails fast if they are missing. Keep secrets in appsettings.Development.json (gitignored) or in user secrets, not in source control. The required keys are:
  • MicrosoftAppTenantId, MicrosoftAppId, and MicrosoftAppPassword for the Agents SDK and Bot Service authentication.
  • BlobStorage:AccountUri or BlobStorage:ConnectionString. One of the two is required for answer storage.
  • ApiSecurity:ApiKey, the shared secret checked against the X-Api-Key request header.
  • TokenValidation and Connections settings for token validation and the service connection.
  • DeliveryChannels settings for Email, Jira, and Slack, needed only for the channels you enable.
Do not commit credentials. Store the API key, app password, and channel secrets in appsettings.Development.json or in user secrets, both of which stay off source control.

Deploying to Azure

The service ships with Terraform under terraform/ and a scripts/Deploy.ps1 deployment script. Provision the infrastructure first, then deploy the application:
cd terraform
terraform init
terraform plan
terraform apply
.\scripts\Deploy.ps1
Terraform provisions the resource group, the Entra ID app, the App Service Plan and App Service, and the Bot Service with its Teams channel. If you already have an Entra ID app, set create_azuread_app = false and supply the existing app ID and password in your Terraform variables instead of letting Terraform create one. After terraform apply, read the bot credentials from the Terraform outputs and use them in your local configuration.