Message Extension Link Unfurling with Cache and Auth Prompt
Design a Teams link-unfurling message extension that resolves internal URLs (incidents, runbooks, dashboards) into rich preview cards, with per-user auth, response caching, and graceful fallbacks for links the user cannot access.
- Target user
- Engineers building link unfurling for internal ChatOps tooling
- Difficulty
- Intermediate
- Tools
- Claude, ChatGPT
The prompt
You are a senior platform engineer who has built a Teams link-unfurling message extension for internal incident and runbook URLs and dealt with the auth and caching edge cases in production. I will provide: - The URL domains/patterns you want to unfurl: [URL PATTERNS] - The backend that resolves a URL to metadata, and its auth model: [BACKEND AND AUTH] - Expected unfurl volume and how often the same link is pasted: [VOLUME AND REPEAT RATE] - Whether different users have different visibility into the linked resource: [PER-USER ACCESS] Your job: 1. **Register the domains** — show the messageHandlers / link unfurling configuration in the app manifest so Teams sends a composeExtension/queryLink invoke for matching URLs. 2. **Handle queryLink** — on the invoke, parse the URL, resolve metadata from [BACKEND], and return a preview Adaptive Card; show the invoke handler shape and the response envelope. 3. **Per-user auth** — because unfurling runs as the pasting user, use SSO / on-behalf-of to call the backend with that user's identity, so the preview reflects what THEY are allowed to see, not a service account's view. 4. **Access fallback** — if the user lacks access to the linked resource, return a minimal "you don't have access" card rather than leaking title/details or erroring out. 5. **Cache** — cache resolved metadata keyed by URL + user-visibility scope with a short TTL, so the same link pasted ten times in a channel does not hit the backend ten times; respect the auth scope in the cache key so you never serve one user's view to another. 6. **Latency budget** — Teams expects a fast invoke response; if the backend is slow, return a lightweight card and avoid blocking. Output as: (a) the manifest messageHandlers block, (b) the queryLink invoke handler pseudocode, (c) the SSO/OBO flow for per-user resolution, (d) the access-fallback card, (e) the cache-key and TTL design. Bias toward: per-user identity for resolution, access-scoped cache keys, and a fast fallback over a slow blocking call.
Why this prompt works
Link unfurling looks trivial until you remember that the preview runs with the pasting user’s identity and that the same link gets dropped into a channel a dozen times during an incident. This prompt makes both facts central. It forces the design to resolve metadata via SSO / on-behalf-of so each preview reflects what that user is allowed to see, which is the difference between a helpful integration and an accidental data-leak channel.
The caching instruction is where this prompt earns its keep. Naive caching keyed on the URL alone is a security bug: it serves one user’s authorized preview to another user who lacks access. By requiring the cache key to include the user’s visibility scope and a short TTL, the output gives you the performance win (ten pastes, one backend call per scope) without the leak. The access-fallback requirement closes the loop — when a user can’t see the resource, they get a clean “no access” card instead of a leaked title or a raw error.
Anchoring everything in the queryLink invoke contract and Teams’ fast-response expectation keeps the result grounded in how the platform actually behaves. You get the manifest block, the handler shape, the OBO flow, and the cache design as concrete artifacts, so the extension you build is both quick under load and safe across an org with mixed permissions.