Skip to content
🎉 Launch sale:50% off everything over $22 — automatically applied at checkout· ends Aug 2Shop the sale →
DevOps AI ToolKit
Newsletter
All guides
AI for Microsoft Teams By James Joyner IV · · 8 min read Last reviewed Jul 2026

Microsoft Teams Error Guide: 'Unable to reach app. Please try again.' Message Extension Timeout — Respond Within 15s

Quick answer

Fix the Microsoft Teams message extension 'Unable to reach app' timeout: return the invoke response within 15 seconds, cache searches, and offload slow work.

  • #microsoft-teams
  • #adaptive-cards
  • #troubleshooting
  • #errors
Free toolkit

Stuck on this Microsoft Teams error? Get the free incident triage checklist

A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.

Overview

When a Teams message extension (search or action command) takes too long to respond to an invoke activity, the Teams client gives up and shows a generic failure. The bot channel and your logs show the request timing out before your handler returns a response.

Unable to reach app. Please try again.

(bot log) composeExtension/query invoke did not return within 15s;
Teams closed the request. Upstream: context deadline exceeded (15000ms).

Symptoms

  • A message extension search box shows “Unable to reach app. Please try again.” after a spinner.
  • Action commands open the task module but the submit hangs, then errors.
  • Intermittent failures that correlate with a slow downstream API or a cold-started function.
  • The bot’s HTTP endpoint logs the composeExtension/query or composeExtension/submitAction invoke arriving, but the response is sent after the client already disconnected.
  • Works locally with a fast backend, fails in production behind a slow dependency.

Common Root Causes

  • Synchronous slow work — calling a slow API, database, or LLM inline before returning the invoke response, exceeding the ~15s invoke budget.
  • Cold starts — the bot runs on a serverless plan that cold-starts beyond the timeout.
  • No caching — every keystroke in a search command hits an expensive upstream with no debounce/cache.
  • Blocking auth — performing an interactive sign-in or token exchange on the critical path instead of returning a silentAuth/auth response quickly.
  • Downstream throttling — the backing service returns 429s and your retry loop blows the budget.

Diagnostic Workflow

Confirm the invoke type and measure end-to-end handler latency. The incoming activity looks like:

{
  "type": "invoke",
  "name": "composeExtension/query",
  "value": { "commandId": "searchIncidents",
             "parameters": [{ "name": "query", "value": "payments" }] }
}

Return the response fast and shape it correctly (a search result list):

# Your bot must reply to the invoke with an HTTP 200 body like this,
# well under the 15s window:
cat <<'JSON'
{
  "composeExtension": {
    "type": "result",
    "attachmentLayout": "list",
    "attachments": [
      { "contentType": "application/vnd.microsoft.card.thumbnail",
        "content": { "title": "INC-4821 Payments latency", "text": "SEV2 open" } }
    ]
  }
}
JSON

Add timing around the handler and the downstream call:

[invoke] composeExtension/query received  t=0ms
[query]  upstream search start           t=40ms
[query]  upstream search returned         t=16200ms   <-- over budget
[invoke] response sent                    t=16240ms   (client already gone)

For search commands, cache and debounce so repeated keystrokes don’t re-hit the upstream:

cache key = commandId + normalized(query)   TTL = 60s
serve cached result instantly; refresh in background

Example Root Cause Analysis

An incident-lookup message extension let engineers search tickets from the Teams compose box. Each keystroke fired a composeExtension/query that called an internal search service, which itself fanned out to three systems. Under load the fan-out took 12-18 seconds, so roughly a third of searches showed “Unable to reach app.”

The handler was doing all the work synchronously on the invoke path. The fix was two-fold: add a 60-second in-memory cache keyed by the normalized query (most searches repeated the same few terms), and cap the upstream call with a hard 3-second deadline, returning a “still indexing, refine your search” result card if it wasn’t met instead of blocking. They also moved the bot from a cold-starting consumption plan to an always-warm instance. Timeouts dropped to near zero.

Prevention Best Practices

  • Return the invoke response within the ~15s window, always; treat it as a hard budget.
  • Put a strict deadline (a few seconds) on any downstream call and return a partial/“refine” result on timeout.
  • Cache and debounce search-command queries; identical keystrokes should not re-hit the backend.
  • Keep the bot warm (avoid cold starts) for latency-sensitive extensions.
  • Offload heavy work: return a quick acknowledgment card, then update via proactive message when done.
  • Handle downstream 429s with a bounded retry that respects Retry-After and never exceeds the budget.

Quick Command Reference

# Tail bot logs for slow invokes
journalctl -u teams-bot --since '15 min ago' | grep composeExtension

# Time the downstream dependency directly
time curl -s "https://search.internal/api?q=payments" >/dev/null

# Confirm the bot messaging endpoint responds quickly
curl -w 'time_total=%{time_total}\n' -o /dev/null -s \
  -X POST https://bot.example.com/api/messages -H 'Content-Type: application/json' -d '{}'

Conclusion

“Unable to reach app” on a message extension almost always means your handler blew the ~15-second invoke budget. The client disconnects even though your code eventually returns a valid response. Treat the invoke path as latency-critical: cap downstream calls with hard deadlines, cache and debounce searches, keep the bot warm, and offload heavy work to a proactive update. Respond fast and the extension stays reliable under load.

Free download · 368-page PDF

Fixed it? Get 500 Microsoft Teams & DevOps AI prompts — free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.

Did this fix your issue?

Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.