Skip to content
CloudOps
Newsletter
All guides
AI for Microsoft Teams By James Joyner IV · · 10 min read

Build Sequential Approval Flows in Power Automate for Teams

Single-approver flows don't survive real change control. Here's how to build multi-stage sequential and parallel approvals in Power Automate, surfaced in Teams.

  • #microsoft-teams
  • #power-automate
  • #approvals
  • #change-management
  • #workflows

The first approval flow I ever shipped to production was a single “Start and wait for an approval” action wired to my team lead. It worked great until the day my team lead was on a plane, the change was a database migration, and nobody else had the authority to greenlight it. The deploy sat in limbo for six hours. That was the day I learned that real change control is almost never one person clicking Approve. It’s a sequence: peer review, then a change manager, then maybe a security sign-off for anything touching production data.

Power Automate handles this well once you stop thinking of approvals as a single step and start thinking of them as stages. This post walks through building sequential and parallel approval flows, surfacing the cards in Microsoft Teams, capturing who said what, and handling the inevitable case where someone never responds at all.

Why single-approver flows fall apart

A one-approver flow encodes exactly one assumption: that person is always available and always has full authority. Both halves of that are wrong in any organization with an audit trail. Change Advisory Boards exist precisely because no single human should be able to wave a production change through. Your automation should mirror your governance, not flatten it.

What you actually want is a pipeline: a trigger kicks things off, stage one gets the technical review, a condition checks the outcome, and only an approval advances the change to the next gate. If anyone rejects, the flow stops and notifies the requester. The structure looks like this:

Trigger (e.g. manual / Teams message / PR merged to release branch)
  -> Stage 1: Start and wait for an approval (Peer review)
  -> Condition: outcome == "Approve"?
       Yes -> Stage 2: Start and wait for an approval (Change manager)
                -> Condition: outcome == "Approve"?
                     Yes -> Deploy step (run pipeline / call webhook)
                     No  -> Notify requester (rejected at change mgmt)
       No  -> Notify requester (rejected at peer review)

Each stage is its own approval action, and the outcome of one feeds the condition that guards the next. That’s the whole pattern. Everything else is detail.

The “Start and wait for an approval” action

This is the workhorse. When you add it, the first field is Approval type, and the choice you make here changes the entire behavior:

  • Approve/Reject - First to respond — any one approver can decide for the group. Good for a pool of on-call engineers where you just need someone to look.
  • Approve/Reject - Everyone must respond — every assigned approver has to approve. This is your parallel approval: assign three people, all three must click Approve. One rejection kills it.
  • Custom Responses - Wait for one response — replace Approve/Reject with your own buttons like “Deploy now”, “Defer to window”, “Reject”. The chosen response comes back in the outcome.
  • Custom Responses - Wait for all responses — same custom buttons, but everyone must answer.

You set Assigned to (semicolon-separated emails or a dynamic field), a Title, Details (Markdown is supported here, so dump the change summary, the PR link, and the rollback plan), and optionally Item link pointing back to the work item.

When the flow runs, the action pauses and waits. Each approver gets a card in the Teams Approvals app, in Outlook actionable email, and on the Power Automate mobile app. They don’t need to open a browser. When responses satisfy the type you chose, the action completes and exposes outputs: outcome, responses (an array with each approver’s response, comments, and timestamp), and the approver identities.

Pro Tip: Put your rollback plan and the exact deploy command in the Details field, not just a link. Approvers click Approve far faster when the thing they’re accountable for is right in front of them, and the Markdown rendering means you can format a clean checklist.

Chaining stages with a Condition on outcome

After Stage 1 completes, add a Condition control. The left side is the dynamic content outcome from the approval; compare it to Approve (or your custom response string). In the If yes branch, drop your Stage 2 approval action. In the If no branch, post a message back to the requester explaining where it died.

The key insight is that outcome is a plain string, so for custom responses you branch on whatever label you defined. A flow with a “Defer to window” button can route to a Schedule/Delay action instead of straight to deploy, while “Deploy now” goes to the pipeline. You’re not limited to a binary gate.

Keep each stage’s outcome and responses outputs because you’ll want them for the audit record. I usually append every stage’s responder, response, and comment into a single string variable and write it to a SharePoint list or Dataverse row at the end, so the change has a complete signed history without anyone keeping a spreadsheet.

Posting an Adaptive Card to a Teams channel

Sometimes you don’t want a private approval card — you want the request visible in a team channel so the whole squad sees it. The Teams connector’s “Post card and wait for a response” action does exactly this: it posts an Adaptive Card to a channel (or chat) and pauses the flow until someone interacts with it.

Here’s a sample card for a deploy gate:

{
  "type": "AdaptiveCard",
  "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
  "version": "1.4",
  "body": [
    {
      "type": "TextBlock",
      "text": "Production Deploy Approval",
      "weight": "Bolder",
      "size": "Large"
    },
    {
      "type": "FactSet",
      "facts": [
        { "title": "Service:", "value": "checkout-api" },
        { "title": "Version:", "value": "2.14.0" },
        { "title": "Requested by:", "value": "@{triggerBody()?['requester']}" },
        { "title": "Window:", "value": "Tonight 22:00 UTC" }
      ]
    },
    {
      "type": "Input.Text",
      "id": "comment",
      "placeholder": "Optional comment / conditions",
      "isMultiline": true
    }
  ],
  "actions": [
    { "type": "Action.Submit", "title": "Approve", "data": { "decision": "approve" } },
    { "type": "Action.Submit", "title": "Reject",  "data": { "decision": "reject" } }
  ]
}

Because each Action.Submit carries a data.decision value, the flow can read it after the card is submitted. The action’s outputs expose the submitted data and the responder. You’d reference the decision with an expression like body('Post_card_and_wait_for_a_response')?['data']?['decision'] and feed that into your Condition exactly like an approval outcome. The optional comment field comes back as ...?['data']?['comment'], and the responder field gives you the identity (display name and email) of whoever clicked, so you always know who made the call.

One caveat: this action waits for a single response. For a “many must agree” channel gate you’re better off with the proper approval action’s “Everyone must respond” type, which tracks each approver individually.

Capturing identity, comments, and an audit trail

Auditors don’t care that a flow ran. They care who approved, when, and what they said. Both the approval action and the card action surface this:

  • Approval responses array: each element has approver, response, comments, and responseDate.
  • Card action: responder plus whatever Input fields you put on the card.

Loop the responses array through an Apply to each, build a clean record, and persist it. If your change management lives in ServiceNow or Jira, this is also where you’d patch the corresponding ticket so the system of record matches what actually happened in Teams.

Timeouts and escalation

Approvals that wait forever are a denial-of-service against your own deploys. Two mechanisms handle this.

First, set a timeout on the action itself. Open the action’s Settings, enable Timeout, and give it an ISO 8601 duration like PT24H (24 hours) or PT4H. When the window lapses, the action fails with a timeout status instead of hanging indefinitely.

Second, catch that timeout and escalate. Add a parallel branch or a subsequent action configured to run after the approval has timed out (in the action’s “Configure run after” settings, tick the “has timed out” checkbox). On timeout, reassign to a backup approver, ping a manager, or auto-reject with a clear “no response within SLA” message. The combination of a hard timeout plus a run-after escalation branch is what turns a fragile flow into one your on-call rotation can actually trust at 3 a.m.

Pro Tip: Use the “Configure run after” settings to build escalation without a tangle of nested conditions. A second approval action that runs only on the first one’s timeout, assigned to the change manager’s backup, is cleaner and more auditable than a Delay-plus-Condition hack.

Letting AI draft the flow logic

Building this by hand in the designer is tedious, and that’s exactly where an AI assistant earns its keep. I treat Claude or GitHub Copilot like a fast junior engineer: I describe the stages, the approver roles, and the timeout policy, and it drafts the Adaptive Card JSON, the run-after escalation logic, and the expressions for reading outcome and data.decision in seconds. The card above started as an AI draft that I then tightened.

But a junior engineer’s work gets reviewed before it touches a tenant, and so does the model’s. Read every connector’s permission scope before you authorize it — an approval flow that quietly has Sites.ReadWrite.All is a bigger blast radius than the deploy it’s gating. Verify the JSON renders correctly in the Adaptive Cards designer, and confirm the expressions resolve against your real trigger schema. Most importantly, never hand the model real tenant credentials, connection strings, or service principal secrets — it doesn’t need them to write flow logic, and pasting them into any chat is how secrets leak. Keep the AI on the logic; keep yourself on the authorization.

If you want a head start, our prompt library and the Teams automation prompt packs include tested prompts for generating Adaptive Cards and approval-flow scaffolding, and the rest of the Microsoft Teams collection covers the surrounding tooling.

Wrapping up

Sequential approvals aren’t more complicated than single ones — they’re just honest about how change control actually works. Chain your approval actions, gate each one with a Condition on outcome, surface the cards where people already work in Teams, capture who decided and why, and never let a flow hang without a timeout and an escalation path. Draft the boring JSON and expressions with AI, then review it like you’d review any junior engineer’s PR before it ships to a tenant. Do that, and your next database migration won’t be stuck on a plane.

build-sequential-approval-flows-in-power-automate-for-teams

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.