Why?
Because adaptive cards are so cool to make and even cooler to introduce to the business, you should also want to be able to reply on an adaptive card post from a Power Automate Flow. Especially from a business point of view the data in an adaptive card is probably no longer relevant after the business process is completed, but the outcome of the process might be something you want to give back to your team 💌.
What?
First let me clarify that this post is a workaround for a functionality that Power Automate should have by default regarding Microsoft Teams Posts: please vote on my idea here: https://powerusers.microsoft.com/t5/Power-Automate-Ideas/Teams-Connector-Reply-to-an-adaptive-card-message-posted-as-the/idi-p/584336
So until that is realised this workaround could get you out of a jam 😎
How?
1) First thing to understand is that this workaround is needed because in the output body of the Post an Adaptive Card to a Teams channel and wait for a response action, there is no message ID where we can reply to 🤯
So our main challenge is to get the message ID of the Adaptive Card Post and then we can reply to that specific Post. In my workaround I introduce an AdaptiveCardIdentifier. This should be a unique string that we can use in the rest of the process to identify the run. I use an expression that includes a timestamp down to the second, combined with some numbering logic that the business process should have (like a sequence number or SharePoint Item ID):
(@{formatDateTime(convertFromUtc(utcNow(), 'W. Europe Standard Time'), 'yyMMddHHmm')}) Post01
Together with the GroupID and the ChannelID of the Microsoft Teams Team and Channel we will be using, I put this identifier in the first three Compose actions because we will be referencing these strings multiple times:
TIP: you can go to Microsoft Teams and copy the link to a channel. Within the copied URL you will find all relevant information 💡.
2) Now we can use the Post an Adaptive Card to a Teams channel and wait for a response action to create our post in the Microsoft Teams Channel. I really like the fact that the Adaptive Card Designer is now part of the Power Automate Flow interface so I use this a lot instead of the older separate Adaptive Card Designer:
Using the outputs of the first and second Compose actions for the Team and Channel properties, the Flow will show Custom value in these properties.
Select the Edit Adaptive Card button to open the integrated Adaptive Card Designer:
My Card Payload as an example copied from the Card Payload Editor:
{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.2",
"body": [
{
"type": "TextBlock",
"text": "Django submitted a Self service Portal request @{outputs('Compose_-_AdaptiveCardIdentifier')}",
"size": "Medium",
"color": "Accent",
"wrap": true
},
{
"type": "FactSet",
"facts": [
{
"title": "Name:",
"value": "Name as string"
},
{
"title": "URL:",
"value": "URL as string"
},
{
"title": "Owner:",
"value": "Owner Email"
},
{
"title": "Description:",
"value": "Description as string"
},
{
"title": "Comments:",
"value": "Comments as string"
}
]
},
{
"type": "Container",
"items": [
{
"type": "TextBlock",
"text": "Please assess this request.",
"size": "Medium",
"weight": "Bolder",
"color": "Accent",
"wrap": true
}
],
"style": "good",
"bleed": true
}
],
"actions": [
{
"type": "Action.OpenUrl",
"title": "Chat",
"url": "https://teams.microsoft.com/l/chat/0/0?users=django.lohn@knowher365.space"
},
{
"type": "Action.OpenUrl",
"title": "Email",
"url": "mailto:django.lohn@knowher365.space"
},
{
"type": "Action.ShowCard",
"title": "Approve",
"card": {
"type": "AdaptiveCard",
"body": [
{
"type": "ImageSet",
"imageSize": "medium",
"images": [
{
"type": "Image",
"url": "https://cdn4.iconfinder.com/data/icons/leadership-and-management/64/Leadership-mission-objective-aim-business-512.png",
"size": "Medium"
}
]
},
{
"type": "Input.Text",
"id": "ApprovalComments",
"isMultiline": true,
"placeholder": "Any comments for Django?"
}
],
"actions": [
{
"type": "Action.Submit",
"title": "OK",
"data": {
"NextStepChoice": "Approved"
}
}
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json"
}
},
{
"type": "Action.ShowCard",
"title": "Reject",
"card": {
"type": "AdaptiveCard",
"body": [
{
"type": "ImageSet",
"imageSize": "medium",
"images": [
{
"type": "Image",
"url": "https://cdn4.iconfinder.com/data/icons/leadership-and-management/64/Manager-company-command-business-manangement-512.png",
"size": "Medium"
}
]
},
{
"type": "TextBlock",
"text": "Categorize your reason for rejection?",
"size": "Medium",
"wrap": true
},
{
"type": "Input.ChoiceSet",
"id": "RejectionReason",
"style": "expanded",
"choices": [
{
"title": "Incorrect information",
"value": "IncorrectInfo"
},
{
"title": "Person not allowed to submit requests",
"value": "PersonNotAllowed"
}
]
},
{
"type": "Input.Text",
"id": "RejectionComments",
"isMultiline": true,
"placeholder": "It would be nice to inform about your rejection."
}
],
"actions": [
{
"type": "Action.Submit",
"title": "Not OK",
"data": {
"NextStepChoice": "Rejected"
}
}
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json"
}
}
]
}
I am still missing the expression integration to reference Flow information, variables and include expressions but you can always open the Card Payload Editor and paste outputs from previous Flow actions here using the familiar syntax like I did:
@{outputs('Compose_-_AdaptiveCardIdentifier')}
TIP: because a Flow that has been run will not show you the adaptive card input, I recommend to copy the full Card Payload using the Adaptive Card Editor and paste it in a Compose action before the Post action for trouble shooting 💡.
Please note that I have put the Should update card property of the action to Yes, because putting our AdaptiveCardIdentifier in the Update message property will give us the opportunity to give feedback to our Team that the adaptive card has been replied to while making sure we can still find the post after completion of the process.
3) After someone replies to the adaptive card, the flow continues and we can use the outputs of the adaptive card in the rest of the flow. You can reference the output of the action directly if you know the right syntax, but for this tutorial I will include the Parse JSON action:
Syntax of the Content property (because sometimes the Body of a Flow action does not show up in the Expression bar):
@{body('Post_an_Adaptive_Card_to_a_Teams_channel_and_wait_for_a_response')}
My Schema to show you the link with the adaptive card structure:
{
"type": "object",
"properties": {
"responseTime": {
"type": "string"
},
"responder": {
"type": "object",
"properties": {
"objectId": {
"type": "string"
},
"tenantId": {
"type": "string"
},
"email": {
"type": "string"
},
"userPrincipalName": {
"type": "string"
},
"displayName": {
"type": "string"
}
}
},
"submitActionId": {
"type": "string"
},
"data": {
"type": "object",
"properties": {
"ApprovalComments": {
"type": "string"
},
"RejectionReason": {
"type": "string"
},
"RejectionComments": {
"type": "string"
},
"NextStepChoice": {
"type": "string"
}
}
}
}
}
Please be very accurate on keeping the structure of the adaptive card aligned with the Schema needed for the Parse JSON action, otherwise the Flow will fail.
4) Now we can get the message ID starting with the Get messages action of the Teams Connector:
Again we are using the outputs of the first and second Compose actions for the Team and Channel properties and therefor it shows the Custom value.
5) We need to get that one message from all the channel messages so we filter the messages from the resulting array using the Filter array action in few steps:
This is to filter the message list for only the messages posted by a bot.
Advanced expression:
@equals(item()?['from']?['application']?['applicationIdentityType'], 'bot')
This is to filter the bot messages to only include the one with attachments (adaptive cards).
Advanced expression:
@greater(length(item()?['attachments']), 0)
This is to filter the bot adaptive card messages to only include the one including our AdaptiveCardIdentifier.
Advanced expression:
@contains(first(item()?['attachments'])?['content'], outputs('Compose_-_AdaptiveCardIdentifier'))
6) Now we have the message ID of the adaptive card post in a fresh array and to make sure that if (by any change) the AdaptiveCardIdentifier is found in multiple message, we only take the first one:
@{First(body('Filter_array_-_ApplicationMessagesBot'))?['id']}
and we can use it in a Compose action for next Flow actions like the Post a reply to a message (V2) action:
In my example the Adaptive Card is posted like:
And after a reply on the Adaptive Card the Teams Thread gets the last response from the Flow:
So again: this is a workaround to get the Message ID. There can be thresholds and limits on very big Microsoft Teams Teams and Channels even though I have not encountered these yet, but once my idea has been accepted (please vote 🙏), it should not be needed anymore.
Any idea why I’m getting this error? It kept working for 2 years and a half until recently.
InvalidTemplate. The execution of template action ‘Filter_array’ failed: The evaluation of ‘query’ action ‘where’ expression ‘@contains(first(item()?[‘attachments’])?[‘content’], outputs(‘ticketNumber’))’ failed: ‘The template language function ‘contains’ expects its first argument ‘collection’ to be a dictionary (object), an array or a string. The provided value is of type ‘Null’.’.
Hi Suraj,
It seems the contains() expression is getting a Null result = blank value.
I checked and the input is not null. I did not change anything in the flow. It stopped working on its own. I wonder if Microsoft changed anything.
Hey Django,
Thanks for the reply.
Again, I had not changed anything and it’s back to normal now. The last 2 runs on Oct28’23 worked well. I’ll monitor and update if I notice those errors again 🙂
thank you for the information
wow good news this is good
Just did this recently in a different way; stumbled upon your blog. It’s weird the MessageID isn’t an “output” for the “Post an Adaptive card” action! Nearly every other Teams action has that. Thanks for your detailed tutorial.
You can do this an easier way, I think, with way less steps. Here’s how I did it.
After your “post an adaptive card and wait for a response” action, create a new “Scope” action.
Within the Scope, put the following 5 actions (1 Teams action and 4 Compose actions):
1. ‘Get messages’ (Teams action)
2. ‘Compose’ (action) named “TeamIDGetter”. Within it, use this expression: outputs(‘Get_messages’)?[‘body’]?[‘value’][0]?[‘channelIdentity’]?[‘teamId’]”
3. Compose (action) named “ChannelIDGetter”. Within it, use this expression: outputs(‘Get_messages’)?[‘body’]?[‘value’][0]?[‘channelIdentity’]?[‘channelId’]
4. ‘Compose’ (action) named “Last Message”. Within it, use this expression: last(outputs(‘Get_messages’)?[‘body/value’])
5. ‘Compose’ (action) named “Last MessageID”. Within it, use this expression: @{outputs(‘Last_Message’)?[‘id’]}
Why does this work without several “Apply to each” functions? Because the first two Compose actions utilize the “[‘0’]” within the value to only pull up the first message from the Team. [‘0’] means “the first object in the array” – and that’s all we need for the TeamID and ChannelID. For the last two ‘Compose’ actions, I use the “last()” function to solely get the last message written (which was the “Post an Adaptive Card and wait for a response”).
Here’s the details for the “Reply with a message in a channel” action (it’s the new one that allows you to post as the Flow bot in a reply).
Post as: Flowbot
Post in: Channel
Message ID: int(outputs(‘Last_MessageID’))
Team: @outputs(‘TeamIDGetter’)
Channel: @outputs(‘ChannelIDGetter’)
Message: whatever you want
Hope this helps anyone out there looking for this. Explains how to reply to an Adaptive Card Teams post!
Thank you for sharing Brett!
That is indeed a nice way to reply to the last post. In my example there could be some steps less but in this explanation, I hope that people understand what is happening better.
The most important difference between our approaches, is that your approach assumes that you want to reply to the last post… in many Teams Channels the adaptive card to which we want to reply is not the last post any more. Maybe someone else started a new discussion in the channel after our adaptive card post. Therefor I introduced an identifier.
Thank you again 💪🏼👍🏼
You, Django Lohn, are the man of the hour! You just made my day!
Thank you so much for posting this!
Thats great Suraj! 💪👍
Post your own adaptive card to Teams and wait for a response trigger will provide the messageID in the dynamic content. You can use that instead of Post an adaptive card…
Hi Bill,
Can you clarify a little bit?
When using the “Post your own adaptive card as the Flow bot to a channel” action –> Message ID is not a property that we get in the dynamic output of the action…