Adaptive Card Approvals in Outlook - Part 2

In Part 1 of this series, we covered the basics of creating Adaptive Cards in Outlook using Power Automate. Now, in Part 2, we’re taking it up a notch by connecting those Adaptive Cards with the built-in Microsoft Approval process. This integration allows users to approve or reject requests directly from their inbox without navigating between apps—streamlining the approval process and boosting productivity.


Step 1: Understanding Approval System Tables

Before you can manually alter or interact with approvals, it’s crucial to understand the four system tables that are triggered at various parts of the approval process.

Approval Table

This is the primary table that captures the status of each approval that is created. Key columns to take note of are:

  • Approval ID: Uniquely identifies each approval instance.

  • Result: The final outcome of the approval process.

  • Stage: Indicates the current stage of the approval, starting as 'Basic'.

  • Status: Represents the approval’s current state (e.g., Active).

  • Status Reason: Provides more specific context, such as 'Pending'.

  • Approval Stage Key: Links to other approval-related records.

When an approval is first created, it has a stage of Basic, a status of Active, and a status reason of Pending.

Dataverse Approval Table

Approval Request Table

This table records every person assigned to the approval. If there’s only one approver, one entry is made. Multiple entries are created for multi-approver scenarios. Key columns include:

  • Owner: The person assigned to the approval.

  • Approval Stage Key: Connects to the approval stage.

  • Reassigned From Index: Important when dealing with reassigned approvals (covered in Part 3).

Dataverse Approval Request Table

Approval Response Table

This table tracks when an approver submits a response. Key columns are:

  • Approval ID Index: Links to the original approval.

  • Approval Stage Key: Connects to the stage of the approval.

  • Comments: Approver’s remarks.

  • Owner: Person who submitted the response.

  • Response: Captures the decision made (approve or reject).

  • Status and Status Reason: Indicates whether the response is committed (final status is 'Inactive' with a status reason of 'Committed').

When all required responses are received, the Approval Table updates to Stage: Complete, Status: Inactive, and Status Reason: Completed.

Dataverse Approval Response Table

Flow Approval Table

This table is created when an approval starts via Power Automate. Key columns include:

  • Flow Approval ID: Matches the Approval ID exactly.

  • Flow Notification URI: Tells where to send the HTTP request to complete the approval.

  • Flow Run Sequence ID: Tracks the run order.

  • Status and Status Reason: Tracks the current state of the approval (e.g., 'Active' with reason 'WaitingForApproval').

Dataverse Flow Approval Table

Step 2: Creating the Approval

To begin the process, create a flow triggered from SharePoint, Dataverse, or another data source. This flow serves as the foundation for initiating the approval process and sending actionable cards.

Next, use the "Create an approval" action to define the details of the approval. In this step, specify essential information such as:

  • Assigned To: The person or group responsible for approving or rejecting the request.

  • Title: A descriptive and meaningful title that clearly indicates the purpose of the approval.

  • Link to Item: A direct link to the relevant item in SharePoint or Dataverse for quick reference.

  • Enable Notifications: Set this option to No to suppress the automatic email notification generated by Power Automate. This configuration ensures that the approval notification is exclusively handled through the actionable card, rather than sending a duplicate standard Power Automate email.

Power Automate Create an Approval

Once the approval action is configured, move on to setting up the Action.Http button within your Adaptive Card. This button will pass the Approval ID as part of the request body, along with the Approver’s ID. Including the approver’s ID helps optimize the flow’s performance by directly linking the response to the appropriate user, minimizing processing time and potential errors.

Adaptive Card Action.Http

Additionally, consider adding an Action.OpenURL button to your Adaptive Card. This button can provide a convenient alternative for users who prefer to complete the approval through a web interface instead of directly from the card. Linking to the approval response in this way gives users more flexibility and enhances the overall user experience.

Adaptive Card Action.OpenUrl

Be sure to store the Approval ID in your source record, such as in SharePoint or Dataverse. This allows you to track the approval status and ensures that the ID is readily available for subsequent steps. Properly storing this ID is crucial for maintaining accurate records and facilitating smooth approval tracking.

Update SharePoint List with Approval ID

Finally, incorporate a "Wait for an approval" action connected to the previously generated Approval ID. This step ensures that the flow pauses until the approval decision is made. By including this wait action, you can effectively trigger additional actions based on the outcome of the approval, making the entire process seamless and automated.

Wait for an Approval

Step 3: Card Response

In your previously created flow for your actionable card response, it is essential to set up the interaction with the Approval Process correctly. The first step is to get a row by ID to retrieve the data for the approver. This ensures that you have all the necessary information to connect the approver's response back to the approval record.

Next, you will need to list rows for the Approval Request. Use a filter row with the following condition:

msdyn_flow_approvalrequestidx_approvalid eq 'toUpper(triggerBody()?['ApprovalID'])' and statecode eq 0

This query is crucial because it ensures that you are only pulling active approval requests that match the given ID. By narrowing the results to active records, you reduce potential errors and improve flow performance.

Get Approver and Approval Request Data

Once the approval request is retrieved, you will need to craft the approver response using a Compose action. In this step, you will build a JSON formatted response that contains essential information about the approver, the response date, and the decision made. It is important to note that if multiple approvers are added to the approval, a different email will be sent to each approver. However, since there is only one response thread needed, it is sufficient to generate a single consolidated response. The JSON structure should include the tenant ID, approver's details, and the response date, as shown below:

{
  "responder": {
    "id": "triggerBody()?['ApproverID']",
    "displayName": "outputs('Get_a_row_by_ID_Approver')?['body/fullname']",
    "email": "outputs('Get_a_row_by_ID_Approver')?['body/internalemailaddress']",
    "tenantId": "148b256d-e915-4a16-bb14-xxxxxxx",
    "userPrincipalName": "outputs('Get_a_row_by_ID_Approver')?['body/internalemailaddress']"
  },
  "requestDate": "outputs('List_rows_Approval_Request')?['body']?['value'][0]?['createdon']",
  "responseDate": "utcNow()",
  "approverResponse": "variables('varApprovalStatus')"
}

This JSON object efficiently consolidates all relevant response details, including the approver's name, email, tenant ID, and response date.

After composing the approver response, you need to create another Compose action to format the response summary. The response summary should capture essential details, including the approver's name, email, approval status, request date, and response date. This ensures that all necessary information is stored and available for reference. An example of the response summary is shown below:

Approver: outputs('Get_a_row_by_ID_Approver')?['body/fullname'], outputs('Get_a_row_by_ID_Approver')?['body/internalemailaddress']
Response: variables('varApprovalStatus')
Request Date: outputs('List_rows_Approval_Request')?['body']?['value'][0]?['createdon']
Response Date: utcNow()
Approver Response and Response Summary Compose Step

To finalize the response handling, add a new row for the Approval Response. This action requires inputting key data points, including the approval ID, stage, status, owner, and approval response itself. Make sure to set the status reason to Committed and the status to Inactive to reflect that the response has been submitted and recorded. You can get the Approval Stage Key from the List rows Approval Request Step.

Approval Stage Key: outputs('List_rows_Approval_Request')?['body']?['value'][0]?['msdyn_flow_approvalrequest_approvalstagekey']

Next, retrieve the current approval details by using a Get a Row by ID (Approval) action.

Add an Approval Response and Get Approval Data

Following that, access the flow approval data through a Get a Row by ID (Flow Approval) action. These steps ensure that you are working with the most up-to-date and accurate data.

Subsequently, you need to list rows in the Flow Approvals to identify the appropriate flow approval related to the Wait for an approval step. Use the following filter to find the approval:

msdyn_flow_flowapproval_flowrunsequenceid eq 'outputs('Get_a_row_by_ID_Flow_Approval')?['body/msdyn_flow_flowapproval_flowrunsequenceid']' and startswith(msdyn_flow_flowapproval_flownotificationuri, 'https')

This step is crucial because it isolates the flow approval record that contains the FlowNotificationURI, which is required to complete the approval.

After identifying the correct flow approval, store the FlowNotificationURI as a variable for use in the HTTP request. Use the following syntax:

varFlowNotificationURI: outputs('List_rows_Flow_Approval_Waiting_on_Response')?['body']?['value'][0]?['msdyn_flow_flowapproval_flownotificationuri']
Get Flow Approval

Once the notification URI is saved, update the approval status to reflect that the process is complete. Set the State to Complete, Status Reason to Completed, Result to the approval status variable, and Status to Inactive. Marking the approval as completed ensures that it is correctly recorded and no further actions are required.

Update a row Approval

Next, you need to generate a Response Summary Array using the previously composed approver response. An example of the array format is:

[
  outputs('Approver_Response')
]

This structured array will be sent as part of the HTTP request, consolidating all necessary approval details.

To complete the response, craft a Total Response Array that will be sent in the HTTP request to finalize the approval. The array should include responses, a response summary, completion date, approval outcome, and other pertinent details. An example format is:

{
  "responses": outputs('Response_Summary_Array'),
  "responseSummary": outputs('Response_Summary'),
  "completionDate": "utcNow()",
  "outcome": "variables('varApprovalStatus')",
  "name": "triggerBody()?['ApprovalID']",
  "title": "outputs('Get_a_row_by_ID_Approval')?['body/msdyn_flow_approval_title']",
  "details": "outputs('Get_a_row_by_ID_Approval')?['body/msdyn_flow_approval_details']",
  "requestDate": "outputs('Get_a_row_by_ID_Approval')?['body/createdon']",
  "expirationDate": "outputs('Get_a_row_by_ID_Approval')?['body/msdyn_flow_approval_expireson']",
  "priority": "outputs('Get_a_row_by_ID_Approval')?['body/msdyn_flow_approval_priority']"
}
Response Array

Finally, craft the HTTP response by using the FlowNotificationURI variable as the endpoint. Set the HTTP method to POST, and include the composed response as the request body. This final step will submit the approval decision back to the system, marking the approval as completed.

HTTP Request for Wait for Approval

By following these steps, you ensure that the entire approval process is streamlined and automated, allowing users to complete approvals directly from their inbox with minimal friction.

Finish the flow by responding to the Adaptive Card as described in Part 1 of this series.

Crediting the Experts

A big shoutout to Tomasz Poszytek for his incredible YouTube videos on Microsoft Approvals, which go into depth on the core concepts and configurations. My approach is a bit different, as it focuses on linking the approval process with Actionable Cards to create a seamless user experience. Be sure to check out his content for a comprehensive look at the built-in approval features. Tomasz Poszytek on YouTube

Previous
Previous

Adaptive Card Approvals in Outlook – Part 3

Next
Next

Adaptive Card Approvals in Outlook - Part 1