ODC with Loop Components - Basic Setup and Link Unfurling


In this first tutorial of the ODC with Loop Components series, we will set up our environment by creating an Application registration in Entra and setting up an Azure Bot resource. Then, we will explore the implementation details needed to turn an application URL into an Adaptive Card-based Loop Component, known as Link unfurling. This involves:
App Manifest - The App Manifest is a configuration file that defines our Microsoft 365 app. It specifically configures settings for link unfurling. The App Manifest is packaged with icon resources and then uploaded to Microsoft 365 through the Microsoft Teams app.
Messaging Endpoint - An exposed REST API in ODC that receives link unfurling requests - via the Azure Bot resource -, processes them, and returns the Adaptive Card-based Loop Component.
Demo Application
This article series includes a demo application called "ODC with Loop Components Demo," available on ODC Forge. Be sure to download the version of the application that matches each article in this series.
For this article, you need to install Version 0.1 from ODC Forge.
In the ODC Portal, go to Forge - All Assets.
Search for "ODC with Loop Components Demo".
Click on the Asset (Do not click on Install on the tile!).
Switch to the Version tab and click on Install next to Version 0.1.
Version 0.1 depends on other Forge components:
OAuthTokenExchange - An external logic library that helps retrieve access tokens easily. In this tutorial however we use an action to retrieve the discovery document from Microsoft Entra only.
LiquidFluid - An external logic library that merges data with templates based on the Shopify Liquid Templating language.
UriParser - An external logic library that parses a given URI/URL.
In the demo, you can create and edit auction entries with a title, description, and starting auction price. In Version 0.1, this is the only feature available, but we will expand it throughout the tutorial series.
After this tutorial, we want to be able to paste a link from an auction detail screen into a Teams conversation. This link should expand into an Adaptive Card-based Loop component that looks like this:
In later parts of the series, we will enable interaction with this card, allowing users to bid on the item.
Messaging Endpoint
The Building a Loop Component section in the series' introductory article mentiones that we need to create a Messaging Endpoint REST API to handle link unfurling. If you've read my ODC with Bots for Teams series, you might wonder why we aren't reusing what we built there and why this is a separate tutorial series.
In the ODC with Bots for Teams series, we developed a pattern for Conversational Bots that you can interact with in Microsoft Teams or other chat platforms like Alexa and Slack.
Conversational Bots are one feature that can be configured in an App Manifest and then uploaded to your Microsoft 365 environment.
Link unfurling, which transforms a link into an Adaptive Card-based Loop component, is another feature known as a Message Extension.
Conversational Bots and Message Extensions use the same core technology stack. This includes a Bot resource in Azure with an associated Entra application registration and an exposed REST API in ODC, which serves as the Messaging Endpoint handling requests from either a Conversational Bot channel or a Message Extension added to a Microsoft 365 app.
However, there is a fundamental difference in how Bots and Message Extensions send a response to a channel.
Conversational Bots - Conversational Bots - the Bot handlers to be more precise - send a response to the channel via the Azure Bot Connector API.
Message Extensions - For Message Extensions, the response is directly returned from the Messaging Endpoint REST API.
Conversational Bots
In ODC, we can use one Messaging Endpoint to manage requests from multiple Azure Bot resources and configured channels. The ODC with Bots for Teams series shows how to create this type of multi-bot handler using ODC events. The diagram below illustrates this pattern.
In this diagram, multiple Azure Bot resources use a single Messaging Endpoint REST API within a central ODC application. The Messaging Endpoint authorizes the Bot, stores the incoming activity in an entity, and then triggers an ODC event with details of the incoming activity.
Several other ODC applications can subscribe to this event, process the activity, and then respond to the channel using the Azure Bot Connector API.
Message Extensions
For Message Extensions, we can't use this decoupled pattern because the Messaging Endpoint must respond directly to the channel. Additionally, a single domain, such as your ODC development stage company-dev.outsystems.app
, can only be managed by one - and only one - message extension, and there's no option to include a path. A message extension is defined in the App Manifest. Take a look at the following diagram:
This diagram shows a setup for handling link unfurling and Adaptive Card-based Loop Components.
A single Azure Bot resource with a configured messaging endpoint can manage link unfurling and Adaptive Card actions because the specific domains are listed in the App Manifest. You can create multiple Message Extension App Manifests for one bot resource.
The Messaging Endpoint that receives requests can be set up in a central ODC application with logic to differentiate between the relevant domains based on the domains and even path segments. It then uses service actions from other ODC applications to create or update an Adaptive Card or handle an Adaptive Card action.
While this approach isn't perfect, I used it because I couldn't find a better option.
Prerequisites
Enough with the theory. Let's start by preparing our environment.
Register Entra Application
A bot resource requires an Entra application registration.
In Azure Portal go to App Registrations and click on New registration.
Name - Microsoft 365 Message Extension with ODC
Supported account types - Accounts in any organizational directory (Any Microsoft Entra ID tenant - Multitenant)
Click Register
From the Overview page, copy the values of:
- Application (client) ID
We will need that value in the next step.
Create an Azure Bot
With our application registration complete, we can now create an Azure Bot resource. An Azure Bot resource is a middleware that connects Microsoft 365 apps (and others) with the Messaging Endpoint in ODC.
In Azure Marketplace search for Azure Bot and click Create - Azure Bot.
Bot handle - BotId-<Application ID from Entra App Registration>
Subscription - Select your subscription model
Resource group - Choose a resource group where you want to place the bot resource
Data residency - Global
Pricing tier - Change plan to Free
Type of App - Multi Tenant (corresponds to our Entra App Registration)
Creation type - Use existing app registration
App ID - Paste the Application ID from the Entra App Registration Overview page
App tenant ID - Paste the Tenant ID from the Entra App Registration Overview page
Click Review + Create, review the information the click Create
Wait until the deployment is complete, then click on Go to resource to continue.
Set Bot Profile
In the deployed Azure Bot resource, first go to the Settings - Bot profile menu and give your bot a meaningful display name and description. You can also upload a custom icon here.
Configure Messaging Endpoint
Next switch to the Settings - Configuration menu. The only settings we have to configure here is to provide the FQDN to our Messaging endpoint.
The messaging endpoint FQDN is the combination of your ODC stage base URL and the path you copied earlier from the Messaged endpoint of the demo application.
Messaging endpoint -
https://<ODC stage>.
outsystems.app/ODCwithLoopComponentsDemo/rest/MessagingEndpoint/Messages
Click Apply to save the changes.
Activate Teams Channel
Next, we need to activate Teams channel support in our Bot resource.
Choose Microsoft Teams from the list of Available Channels in the Settings - Channels menu.
Read the Terms of Service and Agree.
In the Messaging tab, select Microsoft Teams Commercial (most common).
Click Apply.
Summary
In the Prerequisites step, we created an Azure Bot resource with an associated Entra application registration. In the Bot configuration, we specified our Messaging Endpoint hosted in the ODC with Loop Components Demo application. Finally, we activated the Microsoft Teams channel.
As mentioned above, an Azure Bot resource in your Azure tenant acts as middleware. This middleware exchanges messages, called Activities, between configured channels and the messaging endpoint. It does not matter if you are building Conversational Bots or Message Extensions. These capabilities, or what your implementation actually does, are specified in the App Manifest.
Microsoft 365 App
The App Manifest document describes and configures the capabilities that this specific app—your implementation—can perform. In the ODC with Bots for Teams series, we covered the Conversational Bot capability, and in this tutorial, we configure a Message Extension capability for Link unfurling.
The App Manifest, along with additional resources like icons, needs to be uploaded to your Microsoft 365 tenant through either Microsoft Teams or the Microsoft Office Admin Portal. We will use Microsoft Teams and will "install" the app for a single user, a process known as sideloading.
Create App Manifest
The demo project includes a template for the manifest file, complete with icons. Download the template archive from Data - Resources in ODC Studio and extract it to a folder.
An app manifest file is a JSON document that follows the unified app manifest schema. Together with the manifest file you will also need two icons.
Full color icon with a size of 192×192
Outline icon with a size of 32×32
Open the manifest.json document from the extracted template in an editor of your choice.
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.19/MicrosoftTeams.schema.json",
"version": "3.0.0",
"manifestVersion": "1.19",
"id": "<Entra Application (client) ID>",
"name": {
"short": "ODCwithLoop",
"full": "ODCwithLoop Demo"
},
"developer": {
"name": "without.systems",
"websiteUrl": "https://without.systems",
"privacyUrl": "https://without.systems/privacy",
"termsOfUseUrl": "https://without.systems/termsofuse"
},
"description": {
"short": "Shows how to build a Loop Component Adaptive Card",
"full": "Create interaktive elements with Adaptive Cards and share it across Microsoft 365 apps."
},
"icons": {
"outline": "outline.png",
"color": "color.png"
},
"accentColor": "#FFFFFF",
"composeExtensions": [
{
"botId": "<Entra Application (client) ID>",
"composeExtensionType": "botBased",
"commands": [],
"messageHandlers": [
{
"type": "link",
"value": {
"domains": [
"<your ODC development stage domain e.g. company-dev.outsystems.app>"
],
"supportsAnonymizedPayloads": false
}
}
]
}
],
"permissions": [
"identity",
"messageTeamMembers"
],
"validDomains": [
"<your ODC development stage domain e.g. company-dev.outsystems.app>"
]
}
Modify the following values to match your environment
id - Application (client) ID from your Entra Application registration
composeExtensions.botId - Application (client) ID from your Entra Application registration
composeExtensions.messageHandlers.value.domains - Add your ODC development stage domain name.
validDomains - Add your ODC development stage domain name.
Save the document, then compress the manifest.json file and the two icon resources into a ZIP archive.
Allow Sideloading
By default users are not allowed to sideload custom apps into Microsoft Teams. This is defined in a Teams Setup Policy that is associated with your user account in the Microsoft Teams Admin center.
In the Microsoft Teams Admin center.
Select Teams apps - Setup policies in the menu
Click Add
Name: OutSystems Bot Developer Setup Policy
Description: Allows an OutSystems Bot developer to sideload Microsoft Teams apps
Click Save
After we have created the policy, we must assign it to one or more users
Select Users - Manage users in the menu
Search your own user account and select it
In the Policies tab click Edit
Select the OutSystems Bot Developer Setup Policy in the Select App setup policy dropdown
Click Apply
Install App to Microsoft Teams
With our package prepared we can now install it to Microsoft Teams. Open your Microsoft Teams client and in the left icon menu click the Apps icon.
On the bottom left click on Manage your apps
In the apps list click the Upload an app button and select Upload a custom app
Select the zip archive you created
Check the details of the app, then click Add
With our app uploaded to Microsoft Teams, we can now proceed to try out the demo application.
Demo Application - First Try
Open the Demo application in your browser and create a new Auction. After saving, click on the entry of the created Auction again, which will take you back to the detail screen. Copy the full URL from the browser.
Open Microsoft Teams and start a chat conversation with another test account - or a collague you want to bother with your tests 😒.
Paste the URL into the message box, and after a short while, a Loop component should appear next to the pasted URL in the message box.
If it didn't work, check the following:
Review your app manifest to ensure you added the correct domain for link unfurling.
Add a breakpoint at the top of the Messages endpoint in the demo application, start the debugger, and check if a request is sent to your REST API.
Verify the messaging endpoint URL in the Bot resource in Azure to ensure it matches the correct full endpoint URL.
Check the botId value to ensure it matches your Entra Application (client) Id.
Before we dive into the implementation details, let's cover one more important part: Adaptive Cards.
Adaptive Cards
When you copy an auction link into a Teams message, it expands into a Loop component based on an Adaptive Card. These are basically JSON-based UI snippets that the host application displays. Host applications are mainly Microsoft 365 apps, but there is also a JavaScript-based SDK to display Adaptive Cards in any web application.
The template for the Adaptive Card JSON document for our Auction looks like this:
{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.4",
"type": "AdaptiveCard",
"metadata": {
"webUrl": "{{webUrl}}"
},
"body": [
{
"type": "TextBlock",
"size": "medium",
"weight": "bolder",
"text": "{{title}}"
},
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"width": "stretch",
"items": [
{
"type": "TextBlock",
"text": "{{description}}",
"wrap": true
}
],
"verticalContentAlignment": "Center",
"spacing": "Medium"
},
{
"type": "Column",
"width": "auto",
"items": [
{
"type": "TextBlock",
"text": "{{price}}",
"wrap": true,
"size": "ExtraLarge",
"weight": "Bolder"
}
]
}
],
"spacing": "None"
}
]
}
This card defines one Text Block containing the title of the auction followed by a table with two columns containing the description and the price.
Note the handlebar placeholders like {{title}}
that will be replaced with values in our implementation as we will see in a bit.
The full reference documentation for Adaptive Cards along with samples can be found at Welcome - Adaptive Cards.
You can create Adaptive Cards either using a text editor or visually using the Adapative Card Designer.
Adaptive Cards in a Message Extension
To expand a link into an Adaptive Card, the Messaging Endpoint must return it in a specific format that looks like this:
{
"composeExtension": {
"type": "result",
"attachmentLayout": "list",
"attachments": [
{
"contentType": "application/vnd.microsoft.card.adaptive",
"preview": {
"contentType": "application/vnd.microsoft.card.adaptive",
"content": {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.4",
"type": "AdaptiveCard",
"metadata": {
"webUrl": "{{webUrl}}"
},
"body": [
{
"type": "TextBlock",
"size": "medium",
"weight": "bolder",
"text": "{{title}}"
}
]
}
},
"content": {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.4",
"type": "AdaptiveCard",
"metadata": {
"webUrl": "{{webUrl}}"
},
"body": [
{
"type": "TextBlock",
"size": "medium",
"weight": "bolder",
"text": "{{title}}"
},
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"width": "stretch",
"items": [
{
"type": "TextBlock",
"text": "{{description}}",
"wrap": true
}
],
"verticalContentAlignment": "Center",
"spacing": "Medium"
},
{
"type": "Column",
"width": "auto",
"items": [
{
"type": "TextBlock",
"text": "{{price}}",
"wrap": true,
"size": "ExtraLarge",
"weight": "Bolder"
}
]
}
],
"spacing": "None"
}
]
}
}
]
}
}
The Messaging Endpoint returns a result of composeExtension for a Link unfurling request, which includes an Attachment. This attachment has two important properties: preview and content. Both are needed for Link unfurling to work. The preview should contain a simplified version of the Adaptive Card, while the content should have the full version. In the example above, the preview only includes the auction title.
Implementation Walkthrough
Now, let's look at the different implementation details that will turn an auction link into a Loop component. The steps in this first tutorial are straightforward:
Handle the queryLink request - Whenever a user pastes a link that matches the domain in the app manifest into a Teams message, the Teams channel sends a queryLink request to the configured Azure Bot resource, which then forwards the request to the Messaging endpoint.
Unfurl - In our Messaging endpoint handler, we will process the request, look up the auction in the database, and finally return a response containing the Adaptive Card.
Open the ODC with Loop Components Demo application in ODC Studio.
Handle Request
Go to Logic - Integrations - REST - MessagingEndpoint and double click on the exposed Messages endpoint that handles inbound requests from the Azure Bot resource.
In a first step we have to determine the specific type of the activity sent to our Messaging Endpoint, because for now we only want to react on Link unfurling requests.
The DeserializeInboundMinimalActivity action deserializes the request's payload and gives us the following information:
type - The type of activity, which is always invoke for Link unfurling.
name - The name of the specific invocation, which is composeExtension/queryLink for a Link unfurling request.
The following switch statement currently has only one conditional path that is executed when type is invoke and name is composeExtension/queryLink. In all other cases we just end the action flow.
DeserializeInboundQueryLinkActivity converts the request into a structure that includes extra details such as the user who sent the request, the channel used, and more. The most important detail is the URL that was pasted, which is found in the Value attribute of the InvokeQueryLinkActivity structure.
The URL is then finally used to prepare a response in the UnfurlAuctionLink action.
Unfurl
The UnfurlAuctionLink performs several actions to create a response that can be sent back to the channel (Microsoft Teams).
Parse - This external logic action extracts the URL from the Link unfurling request.
ParseQueryString - Splits the query string from the URL into a list of name-value pairs.
HasAuctionDetailPath - Checks if the URL is from an AuctionDetail screen by examining the segments of the parsed URL. If not, we simply exit.
FilterAuctionId - Filters the query string name-value pairs for the AuctionId parameter.
RenderAuctionLoop - Uses the URL and the identified AuctionId value to create the response (see below).
Render Auction Response
RenderAuctionLoop retrieves the Auction entity to obtain the record for the requested auction. It then uses this information to create a complete response for the Messaging Endpoint to send back to the channel.
Auction_Get - This action retrieves the record for the given AuctionId from the Auction entity.
The following actions are for merging a response template with data from the auction. The template (available under Data - Resources - AuctionUnfurl.json) contains handlebar placeholders that will be replaced by values using RenderTemplate from the LiquidFluid external logic library.
The template contains the following placeholders
title - Title of the auction
description - Full item description
price - The auction start price
webUrl - The full url referencing the auction in the demo application
This data structure is represented by a structure AuctionCard in Data - Structures - Auction.
First, we construct the Card payload by assigning values from Auction_Get to the structure properties. Next, we serialize the AuctionCard structure, and finally, we use the RenderTemplate action to merge the serialized data structure with the template.
The Messaging Endpoint then returns this rendered response to the channel, and the Adaptive Card-based Loop Component is displayed.
Missing Pieces
Try changing the auction you pasted earlier in the demo application. You will notice that the card does not update. Being "live" is a core feature of a Loop component, so we need to ensure that cards update when the data changes. We will address this in the next part of the tutorial series.
Another missing piece is that you can copy a Loop component between Microsoft Teams conversations, but not, for example, to a Microsoft Outlook message. We will cover this in a later part.
For now, congratulations! You have successfully turned an OutSystems application link into an Adaptive Card-based Loop component.
Summary
In this part of the ODC with Loop Components tutorial series, we set up the prerequisites for a Message Extension in Azure and implemented a Messaging Endpoint that responds to a link unfurling request from a Microsoft Teams channel with an Adaptive Card-based Loop component.
I hope you enjoyed it. Feel free to leave a comment with your questions and feedback.
Subscribe to my newsletter
Read articles from Stefan Weber directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Stefan Weber
Stefan Weber
As a seasoned Senior Director at Telelink Business Services EAD, a leading IT full-service provider headquartered in Sofia, Bulgaria, I lead the charge in our Application Services Practice. In this role, I spearhead the development of tailored software solutions using no-code/low-code platforms and cutting-edge cloud-ready/cloud-native solutions based on the Microsoft .NET stack. Throughout my diverse career, I've accumulated a wealth of experience in various capacities, both technically and personally. The constant desire to create innovative software solutions led me to the world of Low-Code and the OutSystems platform. I remain captivated by how closely OutSystems aligns with traditional software development, offering a seamless experience devoid of limitations. While my managerial responsibilities primarily revolve around leading and inspiring my teams, my passion for solution development with OutSystems remains unwavering. My personal focus extends to integrating our solutions with leading technologies such as Amazon Web Services, Microsoft 365, Azure, and more. In 2023, I earned recognition as an OutSystems Most Valuable Professional, one of only 80 worldwide, and concurrently became an AWS Community Builder.