The Ultimate Guide to Microsoft Teams based approvals
Last updated on September 3, 2023
Where should the approval happen in Teams? On a team channel? Should all team members be able to respond, or just the owners? Would a private message be a better option? If yes, how many users are taking part in the approval simultaneously, one or many?
Those are some of the questions you should ask when you start planning the approval process. Once you know the answers, you can choose the appropriate method for implementing the approval.
In my previous blog post, I showed you a simple example of how I tend to implement approvals for Teams orders. However, there are many more options for and information related to implementing Teams based approvals, and hence I felt like a more thorough blog post about the broader topic was in order.
In this blog post, I aim to cover all the available options, what kind of problems we might face, and how to counter them, namely:
- The two ways of implementing team scoped approvals in Power Automate
- Having only a subset of team members as the approvers
- Sending an approval request to individual users
- Potential problems and how to solve them
- Building approval processes using other tools than Power Automate
I hope you enjoy reading this blog post and will find it useful. I’ll plan to add to this article in the future when more ideas pop into my head or I acquire even more mastery of the topic. If you have something you’d like to comment on or get added to this article, don’t hesitate to comment! Let’s make this a valuable resource for the years to come. 🙂
The two ways of implementing team scoped approvals in Power Automate
When I say team scoped approvals, I mean approvals that are visible to all team members on a team channel. Power Automate offers us two ways for sending approval adaptive cards to Teams channels:
- Create an approval first, send the automatically generated adaptive card to Teams using the Send your own adaptive card as the Flow bot to a channel action, and stop to wait for an approval.
- Use the Post an adaptive card to a Teams channel and wait for an approval action to do all of the steps at once.
There are some individual differences between these methods. Let’s first take a look at creating, sending and waiting for the approval separately, and after that compare it to the post and wait for a response action.
Create an approval, send the adaptive card, and wait for a response — all separately
In my previous blog post, Bring your Teams provisioning order form and approvals where they matter the most — Part 2, I already showed you a detailed example of how you can implement an approval process on a team channel using Power Automate. However, to make this “ultimate guide” complete, I’m going to include the most vital information about that method here as well, and approach it from a more general perspective.
This method consists of three separate foundational actions: Create an approval, Send your own adaptive card as the Flow bot to a channel, and Wait for an approval. One of the nice things about being able to do all of these steps separately is that it allows us to do other actions in between them if required.
Below is an image of the Create an approval action. Here you define what the approval adaptive card will look like, and by whom and how the approval is handled.
The information inserted into the Title, Details, Item link, Item link description and Requestor fields will be available on the adaptive card. When it comes to the details field, there’s a specific syntax you can use for styling the content. It’s the same syntax you’d use for styling TextBlock control content on your custom adaptive cards.
Typically we tend to use the first response applies option. The purpose of having multiple people as the approvers is to have a backup in case someone is unable to respond. In this case, any of the approvers may decide on their own. However, there might be some situations in which you need everyone to agree before a request is approved.
Only the people whose email addresses are included in the Assigned to field can handle the approvals. When you want the entire team to be able to that, it is quite straight forward to define the approvers. We simply need to save all team member email addresses to a variable, separated by a semicolon. We can then use that variable in the Assigned to field like in the picture above.
The Create an approval action automatically generates us the adaptive card JSON based on the information we’ve used in the action configurations. To send the adaptive card to Teams, all we need to do is to specify the team ID, the channel ID and the generated adaptive card in the Send your own adaptive card as the Flow bot to a channel action.
When the adaptive card has been sent to a team channel, we need to use the separate Wait for an approval action to stop and wait for a response. For that, we only need to provide the approval ID generated by the Create an approval action.
The adaptive card generated by the Create an approval action looks similar to the below image. The bulleted list is the content we added to the Details field, so you can absolutely format the text in some other way if you like. Otherwise, the information and the layout of the card is static.
When an approver clicks on either one of the buttons, a Comments field becomes visible, where the approver can write information regarding the decision before submitting the response. You also get access to the comments in your flow. This can be particularly useful in the case of rejection: you might want to let the person know why their request was rejected, and if they need to make any changes before it can be approved.
The last fundamental piece you need in your approval flow is a condition which directs the execution to the correct branch based on the response. You decide what you want to do based on the approval result. The result options are Approve and Reject.
Post an adaptive card to a Teams channel and wait for a response — all in one
The above method used to be the only option in the past. These days we also have the Post an adaptive card to a Teams channel and wait for a response action. Why do we have two different ways of doing the same thing? How do these methods differ from one another?
- With the post and wait for a response action, you don’t specify who has the permissions for handling the approval. Everyone who has access to the team channel can approve the request. With the Create an approval action, you specify a subset of team members to be able to approve the requests. There are some problems related to this, though, which I’ll get to in a moment.
- With the post and wait for a response action, you need to create a full-fledged adaptive card to send. With the create an approval action, you just need to specify simple markup content, and the action generates the adaptive card for you. It requires more effort from you but also gives you more control.
- With the post and wait for a response action, you are encouraged to specify an update message to display after someone has clicked on the buttons. Otherwise, the card will continue to display the buttons even if the approval has already been handled. With the create an approval action, the card will be updated automatically with the information about the responder, outcome and comments.
- With the post and wait for a response action, you don’t have an option to wait for everyone to respond, or to display notifications about new approval requests.
In summary, the beautiful thing about creating an approval with the post and wait for a response action is having more control over the adaptive cards you want to display. On the other hand, creating an approval first, sending it and pausing to wait for it all separately offers you more control over the other features related to the approval. You can do other things between the actions, such as update a SharePoint list item, enable notifications, narrow down the approvers, and wait for everyone to respond if desired.
As I previously mentioned, the action offers you more control over the adaptive card, and what kind of message you want to display after someone submits a response. However, this also requires more effort on your behalf. You need to create the adaptive card JSON yourself by, e.g., using the Adaptive Card Designer, and define the update message instead of having the Create an approval action do all of the work for you. If you don’t specify the update message, only the name of the responder is displayed on the card. And if you don’t opt for updating the card at all, the card will continue to look the same even after someone has handled the approval, which is not a realistic option as it’d lead to confusing user experience.
If you want, it is possible to self-create a very similar appearing card to what the Create an approval action does for you. However, it can be made much more compact without the Power Automate logo at the top and requiring fewer clicks to submit a response with comments.
Click to view the complete adaptive card JSON{ "type": "AdaptiveCard", "body": [ { "type": "TextBlock", "size": "Large", "weight": "Bolder", "text": "Approval for team \"@{variables('DisplayName')}\"" }, { "type": "TextBlock", "text": "Requested by @{triggerBody()?['Author']?['DisplayName']} <@{triggerBody()?['Author']?['Email']}>" }, { "type": "FactSet", "facts": [ { "title": "Date created", "value": "@{triggerBody()?['Created']}" }, { "title": "Link", "value": "[View order](@{triggerBody()?['{Link}']})" } ] }, { "type": "TextBlock", "text": "- **Requestor**: @{triggerBody()?['Author']?['DisplayName']}\n- **Team name**: @{variables('DisplayName')}\n- **Description**: @{triggerBody()?['Description']}\n- **Category**: @{triggerBody()?['Category']?['Value']}\n- **Visibility**: @{triggerBody()?['Visibility']?['Value']}\n- **Allow external users**: @{if(triggerBody()?['Allowexternaluserstogetinvitedto'],'Yes','No')}\n- **Co-owner(s)**: @{join(variables('Owners'),', ')}" }, { "type": "Input.Text", "placeholder": "Enter comments", "isMultiline": true, "id": "Comments" }, { "type": "ActionSet", "actions": [ { "type": "Action.Submit", "title": "Approve", "id": "Approve" }, { "type": "Action.Submit", "title": "Reject", "id": "Reject" } ] } ], "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", "version": "1.0" }
Having only a subset of team members as the approvers
Now, what if you’d like to have only a subset of team members to handle the approvals? Only the owners, perhaps? And prefer not have the team members even see the approval requests? This is where it starts to become more problematic.
When you use the Create an approval action in Power Automate, you are required to specify the users who have permission to take part in the approval. When you then post the approval card on a channel, everyone in that team can see it, not just the approvers. And it doesn’t end there: everyone can also click on the adaptive card buttons regardless of whether they are allowed to handle the approval or not.
When someone without the permissions clicks on the buttons, the approval won’t happen, but the card will start to display the text This request is not assigned to you. The text doesn’t display just for the user who clicked on the button; it will be displayed to everyone, including the people who have the permission to do the approval. Worst of all, the approvers will no longer have the buttons available to handle the approval. In summary, when someone who isn’t allowed to handle the approval clicks on the buttons, no one can process the approval via the adaptive card after that. Luckily, the owners can still click on the approval link that appears on the card, get directed to the Power Automate approvals page, and handle the approval there.
What about the newer Post an adaptive card to a Teams channel and wait for a response action? Does that have the same problem? As I already mentioned before, one of the key differences is that the post and wait for a response action doesn’t require or offer you an option you to specify the people who are allowed to handle the approval. Everyone who can see the card can do it. It means that if you send the card on a team channel, all team members can do the approval.
What if we sent the adaptive card to a private channel? Could we create a private channel, give only the approvers access, and have the approval cards sent there? Now wouldn’t that be great! No one else would be able even to see the approvals, not to mention, click on the buttons except for the approvers, i.e. the members of the private channel. But that would just be too good to be true, right? Unfortunately, it is not possible to send approval requests to a private channel.
The root cause for this problem, I believe, is the fact that we can’t add any type of Teams apps to private channels. It means that the Flow bot — that is responsible for sending the approval cards and relaying the response back to the flow — can’t be added to the channel. The same applies to all custom bots and other types of apps as well.
I’ve started a user voice request about this unfortunate problem. Please, cast your vote for the cause! I can see not being able to add Teams apps to private channels being very limiting in many situations.
Regardless of which activity you decide to use, the bottom line is this: There is no neat way of having only a subset of team members to handle approvals on a team channel. The best option we have is creating the approval separately and specifying which users are allowed to approve. Still, everyone can see the approval information and click on the buttons. No sensitive information can be handled this way, and the approvers might need to navigate away from Teams for managing some of the tasks other members might accidentally click.
So, are we really out of options, or could we still approach this problem some other way? If you don’t want…
- every team member to be able to see and click on the approval requests, or
- to have a separate team for only the approvers and send the approvals there
… you might want to look into addressing the approval request to all of the approvers individually as an own Teams chat message.
Sending an approval request to individual users
In an ideal situation, we’d be able to send one approval card to a group chat consisting of all the approvers. This way, everyone would be easily kept on track about the approval state. But alas, with the Send your own adaptive card as the Flow bot to a user and Post an adaptive card to a user and wait for a response actions we can only send it to a single recipient. Again, this probably comes down to the limitations of the Flow bot: you can’t add it to a group chat.
Luckily, we can still create the approval first, and then send the adaptive card tied to that approval to all of the approvers individually within a loop. When any of the approvers responds, the flow will proceed, as all of the separately sent adaptive cards are tied to that single approval.
The downside of this approach is that when someone responds to the approval request, the adaptive card only gets updated for that person. None of the other approvers knows that the approval request has already been handled.
Even though we get to know the IDs of the messages that contain the adaptive cards, we don’t have the means to update them. Power Automate doesn’t directly offer us actions to do that, and Microsoft Graph doesn’t have operations for updating private chat messages either.
What about the post and wait for a response action? That does allow updating the card, but again, it only does it for the person who responds. Besides, the action is not suitable for having multiple people taking part in the same approval simultaneously as it creates a separate approval on each action execution, and stops to wait immediately after sending the card. It works in a loop by sending the approval first to one person, waiting for that person to respond, and then it proceeding to send the approval request to the next person. It can be useful for sequential approvals, but for having multiple people taking part in one approval simultaneously, it simply does not work.
Regarding the first option (create and send the approval separately in a loop), I do have a solution for you. It is not as sleek as I’d like it to be, but it was the best I could come up with — so far.
Sending a summary to all the other approvers
In this situation, we are using the “first response applies” option. When any of the approvers responds, we send all of the other approvers another adaptive card which contains a summary of the approval result.
To do it, we first take the information about the original approvers from the Create an approval action and loop through the users. On each loop iteration, we compare, whether the user is someone other than the same one who responded. If yes, we send them an adaptive card which has all the relevant information regarding the approval. For the person who handled the approval, the original approval card has already updated automatically to display the information.
Potential problems and how to solve them
Creating approval processes with Power Automate is typically smooth sailing. However, there are a few situations that can cause you a bit of a headache. Here are some problems I have encountered and how you can avoid them.
-
- If your organization has limited the usage of Teams apps, ensure that the Flow bot is not amongst the blocked apps. Whenever you use the Teams actions to send an adaptive card to a user/channel, the Flow bot app is automatically added for that user or to that channel. If it is not amongst the allowed apps, adding the Flow bot to the channel/chat fails, and your adaptive card will not get sent.
-
- When selecting the team and the channel to send the adaptive card to, ensure the channel is not private. You can’t send an adaptive card as a Flow bot to a private channel. If you try to do that you’ll get an error:Forbidden. The request failed. Error code: ‘BotRequestFailed’. Error Message: ‘Request to the Bot framework failed with error: ‘{“error”:{“code”:”BotNotInConversationRoster”,”message”:”The bot is not part of the conversation roster.”}}’.’.
The error most likely occurs because you can’t add the Flow bot app (or any other Teams app, for that matter) to a private channel. Don’t ask me why. Maybe there is a security concern or a technical limitation? I don’t know.
I’ve created a user voice request for making it possible. If you haven’t voted for it already, please do it now, so we’ll be able to utilize private channels for these kinds of things in the future!
- When selecting the team and the channel to send the adaptive card to, ensure the channel is not private. You can’t send an adaptive card as a Flow bot to a private channel. If you try to do that you’ll get an error:Forbidden. The request failed. Error code: ‘BotRequestFailed’. Error Message: ‘Request to the Bot framework failed with error: ‘{“error”:{“code”:”BotNotInConversationRoster”,”message”:”The bot is not part of the conversation roster.”}}’.’.
-
- The service account (the Power Automate flow author) needs to have a Microsoft Teams license and an Exchange Online license (to get an email inbox, which is required) for all the actions to function correctly. You might also need the Flow per user license if you are using any premium connectors, such as the HTTP or the Azure Key Vault connector.
- In the past, I’ve received an error “Unable to reach app. Please try again.” when attempting to approve an order via the adaptive card. Based on my experience, this is a temporary issue, or it occurs if the person who made the order (requestor) doesn’t have a Teams license.
Building approval processes using other tools than Power Automate
Sometimes you do not want to use Power Automate for the approval process. You might, for example, be concerned about the licensing costs if you are using any of the premium connectors, and your flow is triggered directly by a user. So, what other options do we have?
Azure Logic Apps
Azure Logic Apps does not have any of the approval related actions, such as Create an approval. Because of this, we can’t create an approval first, then send it to Teams, and finally, pause to wait for a response, all separately.
However, we still have the Post an adaptive card to a Teams channel/user and wait for a response actions. Because of this, creating approvals to be handled by all team members on a channel is still entirely doable with Logic apps. However, it gets more complicated if you want to have multiple individual users take part in the same approval via private chat simultaneously.
As I mentioned earlier, if you were to loop through the approvals and Post an adaptive card to a Teams user and wait for a response, the logic app would immediately stop to wait for the response from the first user. It’d proceed to the next user only after receiving a response from the first one.
You should be able to work around this functionality, though. You could do it by, for example, calling another logic app asynchronously within a loop. The different instances of that other logic app would then handle sending the individual approval requests, and continue as the individual users respond.
For this to work correctly, you’d also need to track the collective approval status in a separate data source somewhere. Initially after a response, you’d check the data source to see if the other approvers have already responded, and then proceed accordingly.
Custom Teams bots
Instead of having the Flow bot manage the communication between your Power Automate/Logic App solution and Teams, you could also create your own Teams bot. You might want to create a custom Teams bot instead of using the Flow bot if…
- You want multiple individual users to take part in the approval process via a group chat. Based on the Microsoft Bot Framework documentation, you should be able to create a group conversation with two or more users and your bot. Mind you, I have not yet tried this in practice. I’ll update this blog post with more information when I get around doing that.
- You want to send the approval request to multiple users individually and update the original card when any of them responds. Again, I’m simply referencing the documentation here as I have not yet had a chance to try this out in practice.
- You want to handle the approvals on a team channel, but don’t want all team members to be approvers. If you, for example, want only the owners to be able to respond, you could easily check if the user who has responded is a team owner, ignore their response if they are not, and update the card to hide the buttons only if an owner responds. In other words, going around the problems that currently exist in Power Automate.
At the heart of your bot, is a web service that needs to create the approval message, send it to Teams, and handle the user’s response. Creating the approval process using a custom bot takes a lot more work than implementing it using Power Automate and then Logic Apps. On the flip side, you also have a lot more control over the details.
When you are thinking about implementing an approval process, I’d advise you to do it using Power Automate and Logic Apps primarily. They make things just so much faster. However, if some of the quirks are a dealbreaker to you, then take a look at creating a custom bot.
Microsoft Graph
Microsoft Graph also offers us a way to create channel messages with adaptive cards. However, even though you can easily include buttons in these adaptive cards, you still need an API in the background — a place where to submit the approval response when a user clicks on those buttons. Sending adaptive cards to channels via Microsoft Graph can be useful if you just want to display a message. However, for two-way communication, a bot is the tidiest option.
As a final side note, you can only create a new 1:1 message in an existing chat, and we can’t create new chats via Microsoft Graph. We can list existing chats to figure out the ID of the chat where we want to send a message. Still, the operation supports only delegated permissions, so you can only list the chats where the logged in users is participating.
Afterword
What do you think of the different approaches presented in this article? Do you have something you’d like to add? Let me know in the comments!
As this is supposed to be an “ultimate guide,” I’ll continue adding more information and updates here as they come. I genuinely want this to be an article where you want to return to time and time again!
I hope you enjoyed reading this blog post — it is much appreciated as always! If you’d like to consume more content from me, feel free to follow me on Twitter, YouTube, LinkedIn and Github. Also, if you’d like to get a sneak peek at what’s going on behind the scenes, make sure you sign up for my Insider (news)letter via the sidebar.
Until next time!
Laura
Hi Laura,
I have a challenge here, I am trying an ‘Everyone Must Approve’ approval flow. The flow fits perfectly to my use case but what i am being requested by the users is that want to see where such an approval flow is held up due to someone not approving. I am unable to provide this info because the ‘Everyone Must Approve’ option doesn’t allow us to track the user status for individual approvers.
Is there a way we can achieve this without changing the Approval flow type, i have not been able to find good answers yet.
Thanks,
Mudit
Hi Mudit,
Sounds like you might need to resort to using individual approvals and tracking the approval status (who are the approvers, which ones have/haven’t approved) in an external data source. Then you check that external data source every time someone responds to figure out whether you can move on or if you need to wait for more responses. The same data source could also be used for checking, which users are yet to respond.
Laura
Ultimate indeed.
Very professionally written, updated, informative. Does not leave anything to be surprised at, or anything to figure out the hard way.
Excellent piece.
I think I have just found a new favorite blog to follow.
Aww, Gil!!! Thank you for the great compliments. 🙂 I hope you enjoy the rest of the content as well.
Laura
Hi, I’m having problems with the approve and reject buttons to make them work. When I click approve, an error message arise with the legend, Unable to reach app. Please try again.
Any clue?
thanks!
Hi Reggie,
Make sure that the service account and the people involved in the approval all have Teams licenses. If they do, then it might be a temporary issue. There was a mention about this in the blog post itself as well.
Laura
Brilliant stuff, Laura.
I was thinking how about to create a URL on adaptive card that takes user to his own Flow Approvals tab, instead approving in the channel…
Hi Unal,
Great to hear that you are enjoying the content! 🙂 About your question: After you’ve used the “Create an approval” action, there is a “dynamic content” field for called “Respond link” which contains the URL to the Flow Approvals tab.
Laura
Dear Laura,
thank you for this thorough post! We are just now looking at how we would like to implement some approvals in Teams and have recognized many of the same problems you have described. Our biggest consideration at the moment is, however, how to update the decisions in our CDS, which would require premium licenses for Power Automate. At the moment I’m inclined to save the decisions in a SharePoint custom list with the Flow, after which a Logic App would pick them up and update the entities in CDS. It should also be possible to use MS Graph and delta query for reading the decisions from the Teams channel, called from a Logic App. But for that I would have to save the delta query link somewhere anyway between the runs. So for me it sounds way too baggy, compared to using the SharePoint list. Any thoughts about that?
Cheers from the Alps (almost at least),
Jari
Hi Jari,
Using a SharePoint list sounds like a good option to me as well.
Laura