We all know that feeling with certain workplace apps. The despair of too many clicks, everything loading so slowly... as if this app was actively designed to make your life harder. 🤨😩
Even for modern apps it can be hard to please everyone, from a novice to a power user. Luckily, if you have even just a little development experience, you can create your own micro app that works just for you – using Adaptive Cards. In this case study, we'll take a practical look at a CRM, identify a pinch point with its UI and fix it with the power of an AI chatbot.
Related: Thinking of building with Adaptive Cards? 7 things you need to consider
I use Hubspot a lot in my job. It's a perfectly serviceable app that I have lots of good things to say about.
However, the way we I personally use it, to qualify daily sales leads, is super tedious. Broadly, I start with a list of my new contacts that I need to qualify.
Unfortunately just looking at the people in my grid isn't enough for me to filter them, so I need to open each contact one at a time and look at their full record.
Clicking on each record takes a few seconds for it to load (somewhere around ~5-6 seconds), and a similar duration (~3-4 seconds) is spent when I return to my list.
Once on the record's page, the fun really begins.
Hubspot collects (de-personalized) usage metrics for how each contact uses our software. I hope I'm not boring you, but the process is that – in order to make our decision – I need to scan the sections in the middle and on the right (shown in green). In the best case, when all goes well and the contact is very interested, we have to make a total of four clicks (shown in yellow) to qualify the contact, kick off workflows, and return to the main list. Alternatively, I either message the lead directly if they're high profile, or delete them if they're junk (shown in purple).
What can be improved?
In a majority of cases, I only really use a small fraction of Hubspot's features. There is many dropdowns, tabs and sections that I don't usually need. So if I wanted to redesign this, I would take just the "activity" areas (shown in green) and put them on an Adaptive Card. Then I would put my most popular actions right beneath that, i.e. update status, delete or message.snag
Taking the above points on-board I created a wireframe of how I would design an ideal Adaptive Card for my use case. It looks like this:

Creating the Adaptive Card schema
Re-creating a mockup like this is actually pretty straight forward with the Adaptive Card designer. After a few minutes of work I came up with the below template. You can copy-and-paste this over to the Adaptive Card designer if you want to explore it for yourself.
{ "type": "AdaptiveCard", "version": "1.2", "body": [ { "type": "ColumnSet", "spacing": "Medium", "columns": [ { "type": "Column", "width": "auto", "items": [ { "type": "Image", "url": "${thumbnail}", "$when": "${length(thumbnail)>0}", "width": "40px", "spacing": "None", "horizontalAlignment": "Center", "style": "Person" } ] }, { "type": "Column", "$when": "${length(thumbnail)>0}", "width": "stretch", "height": "stretch", "horizontalAlignment": "Center", "spacing": "Small", "items": [ { "type": "FactSet", "facts": [ { "title": "${title}", "value": "**${description}**" } ], "$when": "${if(title, true, false)}" }, { "type": "FactSet", "facts": [ { "title": "${email}", "value": "**${description}**" } ], "$when": "${if(title, false, true)}" }, { "type": "TextBlock", "text": "${requested}", "wrap": true, "fontType": "Default", "size": "Large", "spacing": "Small" }, { "type": "FactSet", "facts": [ { "title": "Created ${ago}", "value": "Current status: ${lead_status}" } ], "spacing": "Small" } ] } ], "selectAction": { "type": "Action.OpenUrl", "url": "${link}", "id": "open-contact-${id}" } }, { "type": "Container", "items": [ { "type": "ColumnSet", "columns": [ { "type": "Column", "width": "180px", "items": [ { "type": "ColumnSet", "columns": [ { "type": "Column", "width": "180px", "id": "cmdTab1", "items": [ { "type": "TextBlock", "text": "Activities", "weight": "Bolder", "size": "Medium", "horizontalAlignment": "Center" } ], "selectAction": { "type": "Action.ToggleVisibility", "targetElements": [ "cmdTab1", "cmdTab1Selected", "cmdTab2", "cmdTab2Selected", "tab1", "tab2" ] }, "isVisible": false, "separator": true, "minHeight": "25px", "horizontalAlignment": "Center", "verticalContentAlignment": "Center" }, { "type": "Column", "width": "180px", "minHeight": "25px", "items": [ { "type": "TextBlock", "text": "Activities", "color": "Accent", "id": "cmdTab1Selected", "size": "Medium", "weight": "Bolder", "horizontalAlignment": "Center" } ], "verticalContentAlignment": "Center", "selectAction": { "type": "Action.ToggleVisibility" } } ], "spacing": "Medium" } ], "verticalContentAlignment": "Center" }, { "type": "Column", "width": "180px", "items": [ { "type": "ColumnSet", "columns": [ { "type": "Column", "width": "180px", "minHeight": "25px", "items": [ { "type": "TextBlock", "text": "Details", "size": "Medium", "weight": "Bolder", "horizontalAlignment": "Center" } ], "id": "cmdTab2", "selectAction": { "type": "Action.ToggleVisibility", "targetElements": [ "cmdTab1", "cmdTab1Selected", "cmdTab2", "cmdTab2Selected", "tab1", "tab2" ] }, "horizontalAlignment": "Center", "verticalContentAlignment": "Center" }, { "type": "Column", "width": "180px", "minHeight": "25px", "items": [ { "type": "TextBlock", "text": "Details", "color": "Accent", "size": "Medium", "weight": "Bolder", "horizontalAlignment": "Center" } ], "id": "cmdTab2Selected", "isVisible": false, "horizontalAlignment": "Center", "verticalContentAlignment": "Center", "selectAction": { "type": "Action.ToggleVisibility" } } ], "spacing": "Medium" } ], "verticalContentAlignment": "Center", "height": "stretch" } ] } ], "spacing": "Medium", "verticalContentAlignment": "Center", "horizontalAlignment": "Center" }, { "type": "Container", "id": "tab1", "spacing": "Medium", "items": [ { "type": "TextBlock", "text": "🔌 ${active_integrations} active integrations", "wrap": true, "size": "Large", "$when": "${active_integrations != ''}" }, { "type": "ColumnSet", "columns": [ { "type": "Column", "width": "50px", "items": [ { "type": "TextBlock", "text": "Time", "wrap": true, "horizontalAlignment": "Right", "weight": "Bolder", "isSubtle": true } ], "verticalContentAlignment": "Center" }, { "type": "Column", "width": "49px", "backgroundImage": { "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAYCAIAAAC0rgCNAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMTZEaa/1AAAAEElEQVQYV2NoaGigHW5oAADAuiQBWxz13QAAAABJRU5ErkJggg==", "fillMode": "RepeatVertically", "horizontalAlignment": "Center", "verticalAlignment": "Center" }, "items": [ { "type": "Image", "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYBAMAAAASWSDLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAOwQAADsEBuJFr7QAAABl0RVh0U29mdHdhcmUAcGFpbnQubmV0IDQuMC4xNkRpr/UAAAAbUExURf///4CAgICAgICAgICAgICAgICAgICAgICAgK5UnXcAAAAIdFJOUwBMVIePw/f799S5VQAAAD9JREFUGNNjYKAtYAxLFYBzLDo6muESHUAAk2IFcQKgHDYQJwHKYQdxCrApYwJxFGDGeXR0tMDtYQovVaCxvwAJCxPcs9XnIAAAAABJRU5ErkJggg==", "width": "35px", "spacing": "None" } ], "spacing": "None", "bleed": true }, { "type": "Column", "width": "stretch", "items": [ { "type": "TextBlock", "text": "Activity", "wrap": true, "weight": "Bolder", "isSubtle": true } ], "verticalContentAlignment": "Center" } ] } ], "minHeight": "150px" }, { "type": "Container", "id": "tab2", "isVisible": false, "spacing": "Medium", "items": [ { "type": "TextBlock", "text": "📇 Contact details", "wrap": true, "weight": "Bolder" }, { "type": "FactSet", "facts": [ { "title": "Email", "value": "${$root.email}" } ] }, { "type": "TextBlock", "text": "🌎 Origin", "wrap": true, "weight": "Bolder" }, { "type": "FactSet", "facts": [ { "title": "${source_label}", "value": "${source}" }, { "title": "${ip_country_label}", "value": "${ip_country}" }, { "title": "${first_page_seen_label}", "value": "${first_page_seen}" } ] } ], "minHeight": "150px" }, { "type": "ActionSet", "actions": [ { "type": "Action.ToggleVisibility", "title": "Update status", "targetElements": [ "lead-update-status-${id}" ] }, { "type": "Action.Submit", "title": "Delete", "id": "delete" }, { "type": "Action.OpenUrl", "title": "Message", "url": "${link}/?interaction=email" } ] }, { "type": "Container", "isVisible": false, "id": "lead-update-status-${id}", "style": "emphasis", "items": [ { "type": "ColumnSet", "columns": [ { "type": "Column", "width": "stretch", "items": [ { "type": "Input.ChoiceSet", "choices": [ { "title": "New", "value": "New" }, { "title": "Contacted", "value": "Contacted" }, { "title": "Interested", "value": "Interested" }, { "title": "Under Review", "value": "Under Review" }, { "title": "Demo", "value": "Demo" }, { "title": "Convert", "value": "Convert" }, { "title": "Unqualified", "value": "Unqualified" } ], "placeholder": "Update Lead status to", "id": "status" } ] }, { "type": "Column", "width": "auto", "items": [ { "type": "ActionSet", "spacing": "Small", "actions": [ { "type": "Action.Submit", "title": "Update", "id": "update" } ] } ] } ] } ], "bleed": true } ], "$schema": "http://adaptivecards.io/schemas/adaptive-card.json" }
The Adaptive Cards looks like this in the flesh.
There is even a row of in-line actions along the bottom that let you either update the status of the contact, delete them or send them an email.
You can even drag in more elements from the sidebar and customize the layout, if you want.
There's just one small problem still: the above template Card isn't yet showing any live data from my actual Hubspot account. That's what we'll take care of in the next step.
Like almost every SaaS app, Hubspot offers an API. This allows you to read out and write back into your Hubspot data in a way that's secure, fast and standardized across the industry.
We've already got a working Adaptive Card, so how do we get a live feed set up?

Just go over to the App Directory page and look for the application you're trying to connect to. In this case for Hubspot just hit Install now which will redirect you to the Digital Assistant Board (and ask you to login if you aren't already).
Some apps, such as Hubspot here, already have built-in Cards that get installed. But you can just ignore these and return back to the Adaptive Card design you still have open.

Back in the Designer hit Save and then refresh the Designer. Now you can click on Connect to data source and select Hubspot from your dropdown list - and perhaps adjust the request URL and scopes to your needs.

Once you save that, you may be prompted to carry out the account linking.
Having selected this Connector, it will now start to project Hubspot's live data into the Sample Data Editor section.
We now use a process called data binding to marry together the layout of the Adaptive Card with the live data from Hubspot. The layout from above should already work somewhat with Hubspot's API, but it's worth pointing out the inner workings – in case you wanted to apply this to your own case.
To bind our data we start by looping our ColumnSet. We do this simply by adding the following code to line 7: "$data": "${$root.contacts}",
This tells the designer to show such a ColumnSet for each entry in the "contacts" array of our sample data JSON.
You know this worked successfully, when you click on Preview Mode and the entries suddenly multiply by however many items are in your API sample. If it didn't work, check out the docs for templating and see if you can debug your Card payload.
Finally, now we can bind the actual data from the sample response we copied to our design.
First we navigate to the the element where we want to show the name and we enter ${properties.firstname.value} ${properties.firstname.value}
:
This tells the Card designer to look for the correct entry in the JSON by navigating through the "tree" and delving deeper into every branch denoted with a .
in the binding command.
In our example the API's root is already set to the contacts
array (defined in line 7), so to get to the first name of each contact the Card Designer needs to:
Go to the
properties
arrayWithin there, find the
firstname
objectRead out the
value
nested within
We follow the same logic for the last name, and the company name below that.
If everything worked, your Card should now show each contact separately.
Once that is done you can just go to your Board where the Card should just wait for you. Alternatively you could teach the Card some AI training phrases by clicking on Add training phrases in the Designer first.
Training phrases are like a catalog of ways the users could ask for this Card with natural language. Even if you train it just a few different variants, like What are my sales leads? or Show me my Hubspot contacts, then you could ask your chatbot for the Card.
The chatbot is available for many different applications such as MS Teams or your Intranet. The Digital Assistant app built-in with Microsoft Teams for example will show you the Card when you ask it one of the Utterances you pre-trained the Adaptive Card with.

You could even embed this Card into SharePoint from the Adaptive Card Designer's Share menu.
Even though the use case for this is, of course, very specific to Hubspot, it shows you the strengths that making your own chatbot offers. It's a simple way to connect various applications to a common interface, and due to Adaptive Cards allows you to customize interactions and layouts completely so they suit the use case at hand.
The number of clicks we talked about in the beginning (to open individual contacts' records, as well as within the contacts page to qualify them) has been dramatically reduced. The number of tabs or page refreshes necessary to work through contacts has gone from several dozens down to zero.
All-in-all, even though it may not be a perfect solution, it was a great improvement for our team and took us all of an afternoon to create, which is more than made up for through the time savings in the future.
~
I hope this guide showed you how easily you can improve the user flow for your applications and make your power users more productive and efficient. Is there a use case you would like to simplify? What challenges do you face that an Assistant could help your users master? Let me know in the comments below.