Wicked Smart Data
LearnInsightsAboutContact
Sign InLet's Build
LearnInsightsAboutContact
Sign InLet's Build
Wicked Smart Data

Intelligence, automation, and expert execution — plus an elite library of free knowledge. We turn complexity into competitive advantage.

Start a conversation

Platform

  • Learning Paths
  • Insights
  • RSS Feed

Company

  • About
  • Contact
  • Work With Us

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Wicked Smart Data. All rights reserved.

Intelligence · Automation · Advantage

All Insights
Power Automate

Automating Time-Sensitive Approval Escalations in Power Automate: Using Do Until Loops, Timeout Conditions, and Teams Notifications to Enforce SLA Deadlines

Standard approval actions in Power Automate send a request and wait — but they don't remind, escalate, or enforce deadlines on their own. This lesson teaches you to build a production-grade escalation system using Do Until loops, UTC timestamp comparisons, and structured Teams notifications that actually drive action before SLAs breach.

🔥 Expert27 min readOct 7, 2026Updated Oct 7, 2026
Automating Time-Sensitive Approval Escalations in Power Automate: Using Do Until Loops, Timeout Conditions, and Teams Notifications to Enforce SLA Deadlines
On this page
  • Introduction
  • Prerequisites
  • Understanding the Core Problem: Why Standard Approval Actions Fall Short
  • The Architecture Before You Build
  • Setting Up Variables and Calculating Deadline Timestamps
  • Sending the Initial Approval and Capturing the Approval ID
  • Architectural Pattern A: Send Approval Without Waiting, Poll Separately
  • Building the Do Until Loop
  • Step 1: Wait for approval response (short timeout)
  • Step 2: Check if approval came in
  • Step 3: Evaluate time thresholds
  • Crafting Teams Notifications That Actually Drive Action
  • Handling the Resolution Phase
  • Multi-Stage Approval Chains with Escalation
  • Persisting State for Resilience
  • Expression Reference for Time Comparisons
  • Hands-On Exercise
  • Common Mistakes & Troubleshooting
  • The Loop Runs Forever Despite Exit Conditions
  • Escalation Notifications Fire Multiple Times
  • Approval IDs Causing "Approval Not Found" Errors
  • Flow Fails After 30 Days on Long-Running Approvals
  • Teams Messages Going to the Wrong Person
  • Summary & Next Steps
  • Automating Time-Sensitive Approval Escalations in Power Automate: Using Do Until Loops, Timeout Conditions, and Teams Notifications to Enforce SLA Deadlines

    Introduction

    Picture this: your company's procurement policy says every purchase order over $10,000 must be approved within 24 hours. Simple enough on paper. But in practice, approvers are in back-to-back meetings, traveling across time zones, or simply swamped with email. The approval sits there, unresponded to, while the vendor's deadline passes. The business misses a discount window. Someone has to scramble. And when leadership asks why the automation didn't catch this, the answer is usually the same — "the flow sent an email and then just... waited."

    That's the gap this lesson closes. A well-designed escalation flow doesn't just route an approval and hope for the best — it actively monitors the clock, fires reminder notifications at defined intervals, escalates to a fallback approver when thresholds are breached, and logs exactly what happened so you can prove SLA compliance. Building this in Power Automate requires you to combine several moving parts: Do Until loops to poll for a response, timeout expressions to calculate deadline boundaries, conditional logic to branch on outcomes, and Microsoft Teams notifications to create a real-time escalation trail that people actually see.

    By the end of this lesson, you'll have built a production-grade approval escalation flow from scratch. You'll understand not just how to assemble the pieces, but why each design decision matters — and where the traps are when you try to take shortcuts.

    What you'll learn:

    • How Do Until loops work internally in Power Automate and how to configure them to exit cleanly on approval response or timeout — whichever comes first
    • How to calculate and compare deadline timestamps using Power Automate's expression language so your SLA logic is genuinely time-aware
    • How to send structured, actionable Teams notifications to approvers and managers at each escalation stage
    • How to build a multi-tier escalation pattern: reminder → escalation → auto-resolution
    • How to track escalation state in a SharePoint list column so your flow is auditable and resumable after a failure

    Prerequisites

    This is an expert-level lesson. You should already be comfortable with:

    • Building basic approval flows — if you need a foundation, start with Building Approval Workflows with Power Automate
    • Using conditions and variables — review Working with Conditions, Loops, and Variables in Power Automate if loops still feel unfamiliar
    • Power Automate's expression language for date manipulation — Mastering Dynamic Expressions and the Power Automate Formula Language: String, Date, and Array Functions for Real-World Data Manipulation covers the functions we'll use heavily
    • Sending Teams messages from flows — see Using Power Automate with Microsoft Teams: Automate Notifications, Approvals, and Channel Messages for connector basics

    You'll need a Power Automate Per User or Per Flow license (the approval connector is a premium connector), access to a SharePoint site for the approval request list, and a Teams channel where escalations will be posted.


    Understanding the Core Problem: Why Standard Approval Actions Fall Short

    When most Power Automate builders first tackle approvals with deadlines, they reach for the built-in timeout on the Start and wait for an approval action. You've probably seen it: expand the settings, enter a duration in ISO 8601 format like PT24H, and the action will stop waiting after 24 hours. That's real and useful.

    But the built-in timeout has a critical limitation: it's binary. Either the approval comes in before the deadline, or it doesn't, and you get a single branch to handle the "timed out" case. What you cannot do with a plain timeout is:

    1. Send a reminder at the 12-hour mark before the timeout fires
    2. Escalate to a secondary approver at hour 20 if the primary hasn't responded
    3. Log each escalation event with a timestamp to a SharePoint record
    4. Let the secondary approver respond within the remaining window of the original 24-hour SLA

    To build any of those behaviors, you need to take explicit control of the timing loop yourself. That's where the Do Until pattern becomes your primary tool.

    Key insight

    The Do Until loop pattern treats time as a first-class variable. Instead of delegating timeout control to a single action, you poll on a schedule, check both the approval status and the clock, and decide what to do next. This gives you fine-grained control that the native timeout setting simply cannot provide.

    The pattern also makes your flow more resilient. If Power Automate's approval service has a brief hiccup and the Wait for approval action doesn't exit cleanly, your loop's time condition acts as a backstop. You get defense-in-depth rather than a single point of failure.


    The Architecture Before You Build

    Before touching the designer, get the architecture clear in your head. For a 24-hour SLA on a purchase order approval, here's the escalation ladder we're building:

    Time Elapsed Event
    0h Approval request sent to primary approver; approval ID stored in a variable
    12h If no response: send Teams reminder to primary approver
    20h If still no response: send Teams notification to primary and their manager; update SharePoint record status to "Escalated"
    24h If still no response: auto-reject (or auto-approve, depending on your policy); send Teams message to finance channel; update SharePoint to "SLA Breached"

    The flow itself has three logical segments:

    1. Setup phase — Initialize variables, calculate deadline timestamps, send the initial approval, store the approval response object in a variable
    2. Monitoring loop — A Do Until loop that runs every 30 minutes, checking elapsed time against each threshold and branching accordingly
    3. Resolution phase — Triggered when the loop exits (either approved, rejected, or SLA breached), performing final SharePoint updates and notifications

    The SharePoint list that backs this flow has these columns:

    • Title (text) — PO number
    • RequestorEmail (text)
    • ApproverEmail (text)
    • ManagerEmail (text)
    • Amount (number)
    • SubmittedAt (date/time)
    • DeadlineAt (date/time)
    • ApprovalStatus (choice: Pending / Approved / Rejected / Escalated / SLA Breached)
    • ApprovalOutcome (text) — stores the final response text
    • ApprovalID (text) — stores the GUID from the approval action

    This list is your audit trail. Every state change writes back to it, so you can build a Power BI report on top of it or satisfy a compliance audit without digging through flow run history.


    Setting Up Variables and Calculating Deadline Timestamps

    Create a new automated cloud flow triggered by When an item is created on your SharePoint approval requests list. This is a natural fit: a new row appears when someone submits a purchase order form, and the escalation logic kicks off immediately.

    After the trigger, your first block of actions initializes the variables the loop will use. Add a Initialize variable action for each of the following — the order matters because later initializations can reference earlier ones:

    Name: varApprovalID          Type: String    Value: (empty)
    Name: varApprovalResponse    Type: String    Value: Pending
    Name: varLoopShouldExit      Type: Boolean   Value: false
    Name: varEscalationTier      Type: Integer   Value: 0
    Name: varDeadlineISO         Type: String    Value: (expression below)
    Name: varReminderISO         Type: String    Value: (expression below)
    Name: varEscalationISO       Type: String    Value: (expression below)
    

    For varDeadlineISO, use this expression, which takes the trigger timestamp and adds 24 hours:

    addHours(triggerOutputs()?['body/SubmittedAt'], 24)
    

    For varReminderISO (12-hour mark):

    addHours(triggerOutputs()?['body/SubmittedAt'], 12)
    

    For varEscalationISO (20-hour mark):

    addHours(triggerOutputs()?['body/SubmittedAt'], 20)
    

    Warning

    These expressions use triggerOutputs()?['body/SubmittedAt'] assuming your SharePoint column returns an ISO 8601 timestamp. If your column stores the submission time as a separate field rather than using the SharePoint item's Created column, double-check the output schema in a test run. Power Automate's addHours() function requires a valid ISO 8601 input — passing a locale-formatted date string will cause a runtime error that's surprisingly hard to diagnose.

    Now write the deadline back to SharePoint immediately. Add an Update item action pointing to your list, setting DeadlineAt to variables('varDeadlineISO'). This is important: if your flow ever fails mid-run and needs to be restarted, the deadline timestamp is already persisted and won't shift forward.


    Sending the Initial Approval and Capturing the Approval ID

    Add a Start and wait for an approval action. Use the Approve/Reject — First to respond type. Key fields:

    • Title: PO Approval Required — [Title from trigger] ($[Amount from trigger])
    • Assigned to: [ApproverEmail from trigger]
    • Details: Build a rich message body that includes the PO number, amount, requestor name, and a direct link to the SharePoint item. The more context you give the approver here, the faster they'll respond — which is the whole point.
    • Item link: Use the SharePoint item's URL from trigger metadata
    • Response options: Leave as default (Approve / Reject)

    Critically, do not set the built-in timeout on this action. You want the action to wait indefinitely, because your loop will handle time enforcement. Setting both a built-in timeout and a Do Until timeout creates a race condition that produces unpredictable behavior.

    After this action, the flow pauses until an approver responds. When it resumes, you have the full approval response object available. Immediately set your variables:

    • Set varApprovalID to: outputs('Start_and_wait_for_an_approval')?['body/approvalId']
    • Set varApprovalResponse to: outputs('Start_and_wait_for_an_approval')?['body/outcome']

    Wait — there's a conceptual issue here that trips up many builders. If you use Start and wait for an approval without a timeout, the entire flow pauses at that action. The Do Until loop below it won't run until after someone responds. That means you can't use this action inside the loop for the initial request — you'd need to restructure.

    The correct architecture is: send the initial approval before the loop with no timeout (letting it wait as long as it takes), and use the loop only to monitor for elapsed time thresholds while the approval is pending. But Power Automate's execution model doesn't let you simultaneously wait for an approval and run loop iterations.

    This is the architectural challenge that separates expert flows from beginner ones, and there are two valid approaches.


    Architectural Pattern A: Send Approval Without Waiting, Poll Separately

    The cleaner approach for escalation flows is to use Start an approval (without "wait") and then use the Do Until loop to poll for its response using Wait for an approval or by checking the approval's status through the connector.

    Here's how this works in practice:

    1. Use Start an approval action — this sends the approval request and immediately returns an approvalId without blocking the flow
    2. Set varApprovalID to the returned approvalId
    3. Update your SharePoint record's ApprovalID column with this value
    4. Enter the Do Until loop, which will:
      • Attempt a short Wait for an approval action with a brief timeout (30 minutes)
      • Check whether a response came in during that window
      • If not, evaluate which time threshold has been crossed and fire the appropriate escalation
      • Update varApprovalResponse and varLoopShouldExit accordingly

    This approach keeps time-check logic and approval-response logic inside the same loop iteration, making the escalation behavior precise to the polling interval (30 minutes in our case).

    Note

    "Precise to the polling interval" means a reminder scheduled for the 12-hour mark might actually fire at 12h 29m if the previous poll completed just before the threshold and the next one completes just after. For most business SLAs, 30-minute precision is more than adequate. If you need tighter precision, reduce the poll interval — but be aware that each loop iteration consumes flow actions against your plan's limits.


    Building the Do Until Loop

    Add a Do Until action after the Start an approval action. In the loop's condition, you want it to exit when either of these is true:

    • varLoopShouldExit equals true
    • The current time is past the deadline

    Set the loop condition to:

    @equals(variables('varLoopShouldExit'), true)
    

    You'll handle the deadline-breach exit condition inside the loop by setting varLoopShouldExit to true when appropriate.

    Configure the loop limits:

    • Count: 50 (this is a safety valve — at 30-minute intervals, 50 iterations covers 25 hours, which gives you buffer beyond the 24-hour SLA)
    • Timeout: PT26H (ISO 8601 for 26 hours — this is the outer failsafe)

    Inside the loop, structure the logic in this order:

    Step 1: Wait for approval response (short timeout)

    Add a Wait for an approval action inside the loop, using variables('varApprovalID') as the approval ID. Set its timeout to PT30M (30 minutes). This action will either return the approval response or time out silently after 30 minutes.

    Configure the run after settings of the next action to run on both success and time out, so the flow continues regardless.

    Step 2: Check if approval came in

    Add a condition action that checks whether the Wait for an approval action produced an outcome. The expression that detects a successful response:

    @not(empty(outputs('Wait_for_an_approval')?['body/outcome']))
    

    If true (approval came in):

    • Set varApprovalResponse to outputs('Wait_for_an_approval')?['body/outcome']
    • Set varLoopShouldExit to true
    • Update SharePoint: set ApprovalStatus to the outcome value (Approved or Rejected)

    If false (still waiting), move to the time-threshold checks.

    Step 3: Evaluate time thresholds

    Inside the "false" branch of Step 2, add a nested condition:

    @and(
      greater(utcNow(), variables('varDeadlineISO')),
      equals(variables('varLoopShouldExit'), false)
    )
    

    If past the deadline:

    • Set varApprovalResponse to Timed Out
    • Set varLoopShouldExit to true
    • Update SharePoint ApprovalStatus to SLA Breached
    • Send the breach notification to the Teams finance channel (details below)

    If not yet past deadline, add another nested condition to check escalation tier thresholds:

    @and(
      greater(utcNow(), variables('varEscalationISO')),
      less(variables('varEscalationTier'), 2)
    )
    

    If past the 20-hour mark and escalation tier is below 2 (meaning we haven't fired this escalation yet):

    • Set varEscalationTier to 2
    • Send Teams message to both approver and their manager
    • Update SharePoint ApprovalStatus to Escalated

    Then check the reminder threshold similarly:

    @and(
      greater(utcNow(), variables('varReminderISO')),
      less(variables('varEscalationTier'), 1)
    )
    

    If past the 12-hour mark and tier is below 1:

    • Set varEscalationTier to 1
    • Send reminder Teams message to approver
    • (Do not change SharePoint status — this is a soft reminder, not a formal escalation)

    Key insight

    The varEscalationTier integer acts as a state machine. By only firing an escalation when the tier is below the threshold, you prevent the same notification from firing repeatedly on every loop iteration once a threshold is crossed. This is the single most common bug in DIY escalation flows — forgetting to track what you've already sent.


    Crafting Teams Notifications That Actually Drive Action

    A notification that says "Your approval is pending" is nearly useless. People see it, mentally register it as noise, and move on. If you want your escalation notifications to produce responses, they need to provide immediate context and a clear next step.

    For the 12-hour reminder, send an adaptive card to the approver via Teams. Adaptive cards let you include rich formatting, the key details of the request, and an Approve/Reject button. For this pattern, use the Post an Adaptive Card and wait for a response action as an alternative approval channel — but only if your architecture supports tracking two possible response channels (the original approval and the card). Simpler but still effective: send a Teams message that includes the direct link to the approval.

    Here's what the 12-hour reminder message should contain, structured as a formatted Teams message using HTML in the message body:

    ⏰ Reminder: Approval Needed — [PO Number]
    
    You have a purchase order approval waiting that was submitted 12 hours ago. 
    SLA deadline: [varDeadlineISO formatted as readable date]
    
    Request Details:
    • Requestor: [RequestorEmail]
    • Amount: $[Amount]
    • PO Number: [Title]
    
    Action Required: Please approve or reject this request within the next 12 hours 
    to meet the SLA deadline.
    
    [Direct link to approval: Click here to respond]
    

    For the 20-hour escalation, send to both the approver and their manager, but in separate messages with different framing:

    Approver message:

    🚨 Urgent: Approval SLA at Risk — [PO Number]
    
    This approval is now 20 hours old and your SLA deadline expires in 4 hours. 
    Your manager [ManagerEmail] has been notified.
    
    Please respond immediately: [approval link]
    

    Manager message:

    🚨 Escalation Notice: Approval SLA at Risk
    
    [ApproverEmail] has not responded to a purchase order approval that expires 
    in 4 hours. If no response is received, the request will be automatically 
    [rejected/escalated to you] at [deadline time].
    
    PO Details:
    • Amount: $[Amount]
    • Requestor: [RequestorEmail]
    • Submitted: [SubmittedAt]
    
    You may respond directly if needed: [approval link]
    

    For the SLA breach notification to the finance channel:

    🔴 SLA BREACH — PO Approval Expired Without Response
    
    PO: [Title] | Amount: $[Amount]
    Original Approver: [ApproverEmail]
    Submitted: [SubmittedAt]
    Deadline: [DeadlineISO]
    
    This request was automatically [rejected] due to no response within 24 hours. 
    Flow run ID: [workflow()['run']['name']]
    

    Including the flow run ID in the breach notification is a practice borrowed from Master Error Handling and Retry Patterns in Power Automate for Bulletproof Flows — it gives your team a direct path to the run history when investigating what happened.

    Tip

    Use formatDateTime() in your expressions to convert ISO timestamps to human-readable formats before inserting them into notification messages. formatDateTime(variables('varDeadlineISO'), 'dddd, MMMM d \'at\' h:mm tt') will render as "Monday, March 24 at 3:00 PM" — infinitely more useful than "2025-03-24T15:00:00.0000000Z" in a Teams message.


    Handling the Resolution Phase

    When the Do Until loop exits — for any reason — you move into the resolution phase. At this point, varApprovalResponse holds one of three values: Approve, Reject, or Timed Out.

    Add a Switch action (not a nested condition tree) on variables('varApprovalResponse'). Switch gives you cleaner branches for three or more outcomes than nested if-else conditions.

    Case: Approve

    • Update SharePoint ApprovalStatus to Approved
    • Update ApprovalOutcome to the approver's comments (from the approval response object)
    • Send a Teams notification to the requestor: "Your PO has been approved — here's what comes next..."
    • If your process involves creating a Planner task for the next step in procurement, this is where you'd add that action

    Case: Reject

    • Update SharePoint ApprovalStatus to Rejected
    • Update ApprovalOutcome to the rejection reason
    • Send a Teams notification to the requestor with the rejection reason and guidance on next steps
    • Send a notification to the finance channel

    Case: Timed Out

    • The SharePoint record was already updated inside the loop, so you don't need to repeat that here
    • Depending on your business policy, you may want to trigger a secondary escalation — for example, sending the request to the approver's manager as an actual approval request (not just a notification) so they can make the call

    Warning

    Never silently auto-approve based on non-response. Auto-approve-on-timeout is a serious audit risk. If your process requires a decision when no one responds, the safest pattern is auto-reject with a note explaining why, combined with a clear resubmission path. This preserves accountability and creates a clean paper trail.

    After the Switch action, add a final Update item action that stamps the SharePoint record with a completion timestamp in a ResolvedAt column. This makes it trivial to calculate actual vs. SLA duration later in Power BI.


    Multi-Stage Approval Chains with Escalation

    Some organizations have two-tier approval requirements — say, department head approval for POs over $10,000 and finance director approval for anything over $50,000. In this case, your escalation logic needs to reset after each tier is successfully approved and then initialize a fresh escalation cycle for the next approver.

    The cleanest way to handle this is with a Scope action that wraps the entire "one tier of approval" logic — variables, Do Until loop, and resolution — and then conditionally run the next Scope based on the first tier's outcome and the dollar amount.

    This is the pattern described in depth in Designing Multi-Stage Approval Chains in Power Automate: Sequential, Parallel, and Delegated Sign-Off Patterns. The key adaptation when adding escalation: you must reinitialize varEscalationTier to 0 and recalculate the deadline variables before each Scope runs. The second-tier approver gets their own fresh 24-hour clock — or a tighter window if the first tier ate into a shared overall deadline.

    For shared-deadline scenarios, calculate all deadline timestamps once at the beginning of the flow (relative to the original submission time), then pass them as variables through each tier. This way, if Tier 1 approval takes 18 hours, Tier 2 only has 6 hours before the overall SLA fires.


    Persisting State for Resilience

    Cloud flows have a maximum run duration of 30 days, but they can fail. If your escalation flow fails at hour 18 due to a transient connector error and Power Automate retries it from scratch, the retry would reinitialize varEscalationTier to 0 and recalculate deadlines from the current time rather than the original submission time — which would give the approver a fresh 24-hour clock. That defeats the entire purpose.

    The fix is to make your flow's restart behavior aware of existing state.

    At the very beginning of your flow (right after the trigger), add a Get item action that reads the current SharePoint record. Then add a condition:

    @not(equals(triggerOutputs()?['body/ApprovalStatus'], 'Pending'))
    

    If the status is anything other than Pending, terminate the flow immediately with a Terminate action set to "Succeeded." This prevents a restarted flow from double-processing a record that already reached resolution.

    For the deadline variables, instead of calculating them from triggerOutputs() on a restart, use the DeadlineAt value that was already written to SharePoint. Add a condition after the variable initialization block:

    @not(empty(triggerOutputs()?['body/DeadlineAt']))
    

    If the SharePoint record already has a DeadlineAt value, set varDeadlineISO to that existing value rather than recalculating it. This ensures deadline persistence across restarts.

    For varEscalationTier, you'll need to infer the current tier from the SharePoint ApprovalStatus column:

    • Pending → tier 0
    • Escalated → tier 2 (skip the reminder, go straight to monitoring for deadline breach)

    This makes your flow genuinely resilient rather than merely hoping that transient failures won't happen. For a deeper treatment of retry and resilience patterns, see Master Error Handling and Retry Patterns in Power Automate for Bulletproof Flows.


    Expression Reference for Time Comparisons

    Time comparison expressions are the engine of the entire escalation pattern. Let's be precise about the functions you'll use, because getting them wrong produces bugs that only surface days after deployment.

    utcNow() — returns the current UTC timestamp as an ISO 8601 string. Always use UTC throughout your flow. Never use a regional timestamp for comparisons — the greater() function does lexicographic comparison on ISO strings, which only works correctly in UTC format.

    greater(utcNow(), variables('varDeadlineISO')) — returns true if current time is past the deadline. This works because ISO 8601 UTC strings sort lexicographically in chronological order.

    addHours(timestamp, hours) — adds hours to an ISO timestamp. Use this for deadline calculations.

    addMinutes(timestamp, minutes) — for finer-grained threshold calculations.

    dateDifference() — not available in Power Automate; use a workaround with ticks():

    div(
      sub(
        ticks(utcNow()),
        ticks(triggerOutputs()?['body/SubmittedAt'])
      ),
      36000000000
    )
    

    This expression calculates elapsed hours by converting both timestamps to ticks (100-nanosecond intervals since January 1, 0001) and dividing the difference by the number of ticks per hour. It's verbose but reliable.

    formatDateTime(timestamp, format) — for human-readable output in notifications. Common formats:

    • 'yyyy-MM-dd HH:mm' → 2025-03-24 15:00
    • 'ddd, MMM d h:mm tt' → Mon, Mar 24 3:00 PM

    Tip

    Store all your pre-calculated deadline strings (varDeadlineISO, varReminderISO, varEscalationISO) in human-readable format as separate variables in addition to the ISO format ones. Use the ISO versions for comparisons and the readable versions for notification messages. This avoids calling formatDateTime() inside notification actions repeatedly, which keeps your expressions readable and your action count down.


    Hands-On Exercise

    Build the following complete escalation flow for a vendor contract review scenario. Specifications:

    Scenario: Legal team receives contract review requests via a SharePoint list. Contracts must be reviewed within 48 hours of submission. Reviews over $500,000 in contract value must also be approved by the General Counsel.

    Build requirements:

    1. SharePoint list — Create a list called "Contract Reviews" with these columns: Title (contract name), ContractValue (number), RequestorEmail (text), LegalReviewerEmail (text), GCEmail (text), SubmittedAt (date/time), DeadlineAt (date/time), ReviewStatus (choice: Pending / In Review / Approved / Rejected / Escalated / SLA Breached), EscalationTier (number).

    2. Flow trigger — When an item is created in Contract Reviews.

    3. Tier 1 escalation ladder:

      • 0h: Send approval to LegalReviewerEmail; update EscalationTier to 0
      • 24h: If no response, send Teams reminder to reviewer; update EscalationTier to 1
      • 42h: If no response, send Teams message to reviewer + GCEmail; update ReviewStatus to Escalated; update EscalationTier to 2
      • 48h: If no response, auto-reject; update ReviewStatus to SLA Breached; post to a Teams channel called "Legal SLA Alerts"
    4. Tier 2 (conditional): If ContractValue > 500,000 AND Tier 1 was Approved, restart the escalation cycle with GCEmail as approver and a fresh 24-hour deadline.

    5. Resilience: Implement the state-restoration logic so the flow doesn't reset deadlines if restarted.

    6. Notification quality: All Teams messages must include the contract name, value, requestor, deadline in human-readable format, and a direct link to the SharePoint item.

    Challenge extension: Add a Planner task for the reviewer at the moment of initial approval request, and mark that task complete when the review resolves (either outcome). Reference Automating Planner Task Creation, Assignment, and Status Updates with Power Automate: End-to-End Project Workflow Integration for the Planner connector details.


    Common Mistakes & Troubleshooting

    The Loop Runs Forever Despite Exit Conditions

    Symptom: The Do Until loop reaches its iteration count limit (50) without exiting, even after an approval response comes in.

    Cause: The Wait for an approval action inside the loop has "run after" settings configured to only run on success. When it times out after 30 minutes, the subsequent condition that checks varLoopShouldExit skips, and the loop never gets the chance to set it to true.

    Fix: Select every action inside the loop and configure its run after settings to run on success, failure, skipped, and timed out. In Power Automate's designer, right-click each action (or use the three-dot menu) to access run after settings. This is one of the most impactful settings in Power Automate and one of the least-used by beginners.

    Escalation Notifications Fire Multiple Times

    Symptom: The approver receives 15 reminder messages instead of one.

    Cause: varEscalationTier is not being set before the condition check, or the condition check uses less than or equal to instead of less than, allowing the same tier to trigger again on the next iteration.

    Fix: Verify that your tier-update action (Set variable) runs inside the conditional branch that fires the notification, and that the condition gates on less(variables('varEscalationTier'), 1) rather than lessOrEquals. Check your run history — in each loop iteration's detail view, you can see exactly which branch executed and what variable values were at that moment.

    Approval IDs Causing "Approval Not Found" Errors

    Symptom: The Wait for an approval action inside the loop fails with "Approval not found" or "ApprovalId is invalid."

    Cause: The Start an approval action returned an approval ID but the approval was already responded to through the email/Teams notification before the loop's Wait for an approval checked for it. The approval is in a completed state, and the wait action doesn't handle that gracefully.

    Fix: Wrap the Wait for an approval action in a try-catch pattern using a Scope with error handling. Configure a parallel branch that runs if the Scope fails, which reads the approval's current status directly. Alternatively, use the approval connector's Get approval action to retrieve status by ID before calling Wait for an approval, and only call the wait if status is still Pending.

    Warning

    This error is far more common than most documentation suggests. In high-traffic flows where approvers are responsive, they'll often approve within seconds of receiving the notification — before the loop even starts its first Wait for an approval call. Always build your loop to handle the "already completed" case.

    Flow Fails After 30 Days on Long-Running Approvals

    Symptom: Approvals that haven't been acted on in 30 days cause the flow to terminate with a "Flow run exceeded maximum duration" error.

    Cause: Power Automate cloud flows have a hard maximum run duration of 30 days. This is an infrastructure limit, not something you can configure around.

    Fix: For approval workflows with possible 30+ day windows, restructure so each loop iteration creates a new child flow run rather than staying inside a single parent run. A scheduled flow that polls a SharePoint "pending approvals" list daily and fires escalations as needed is architecturally cleaner for very long-running scenarios, though it adds complexity. See Scheduling and Managing Time-Based Flows in Power Automate: Recurrence Triggers, Time Zones, and Business Hours Logic for the scheduled polling alternative.

    Teams Messages Going to the Wrong Person

    Symptom: The escalation message is sent to the approver's email address but shows up in a different Teams user's chat, or fails silently.

    Cause: The Teams connector matches users by UPN (User Principal Name), which is usually the same as email but not always — especially in organizations that have undergone domain migrations or mergers.

    Fix: Use the Microsoft 365 Users connector's Get user profile (V2) action to resolve the email to a confirmed UPN before passing it to Teams actions. This adds one action but eliminates a class of intermittent failures that are very difficult to debug after the fact.


    Summary & Next Steps

    You've built a production-grade approval escalation system. The key architectural insights from this lesson:

    The Do Until loop is your time machine. By polling on a defined interval and comparing the current UTC time against pre-calculated threshold variables, you get precise, auditable control over escalation timing that the native timeout setting can't provide.

    State is your resilience mechanism. Writing escalation tier, deadlines, and approval status to SharePoint at every state change means your flow can survive failures, retries, and restarts without losing context or misfiring notifications.

    Notifications need to drive action. Every Teams message should include the context someone needs to respond immediately — not just an alert that something needs attention. The difference between a 12% response rate and a 78% response rate on approval reminders often comes down to message quality, not message frequency.

    The "already completed" case is always possible. Any time you use Wait for an approval after Start an approval, assume that the approval might already be resolved. Build your error handling accordingly.

    From here, your natural next steps are:

    • Extend this pattern with adaptive cards for richer in-Teams approval experiences — Implementing Adaptive Card-Based Human-in-the-Loop Approvals in Power Automate: Dynamic Forms, Contextual Data Injection, and Response Handling covers this in depth
    • Add recurring reminder logic as a separate scheduled component — Automating Recurring Approval Reminders in Power Automate: Escalating Overdue Requests, Notifying Delegates, and Closing Stale Approvals Automatically takes a complementary approach
    • Connect this approval data to a Power BI dashboard for SLA reporting — the SubmittedAt, DeadlineAt, and ResolvedAt columns you built into the SharePoint list give you everything you need for cycle-time analysis

    The escalation pattern you've learned here applies beyond procurement approvals. Any time-sensitive human decision — incident acknowledgment, content review, access requests, compliance sign-offs — can be governed with this same architecture. The variables change; the structure doesn't.

    Work With Us

    From insight to implementation

    Reading is the start. When you're ready to build the data, automation, or AI systems behind it, our team turns strategy into shipped results.

    Let's Build

    Flow Automation Basics

    Previous

    Automating Recurring Approval Reminders in Power Automate: Escalating Overdue Requests, Notifying Delegates, and Closing Stale Approvals Automatically

    Related Insights

    Power AutomateFoundation

    Implementing Connection References in Power Automate Solutions: Mapping, Sharing, and Updating Connector Credentials Across Environments Without Breaking Deployed Flows

    18 min
    Power AutomateExpert

    Automating Recurring Approval Reminders in Power Automate: Escalating Overdue Requests, Notifying Delegates, and Closing Stale Approvals Automatically

    26 min
    Power AutomatePractitioner

    Implementing Exponential Backoff and Circuit Breaker Patterns in Power Automate: Protecting Downstream Systems from Cascading Failures

    24 min

    On this page

    • Introduction
    • Prerequisites
    • Understanding the Core Problem: Why Standard Approval Actions Fall Short
    • The Architecture Before You Build
    • Setting Up Variables and Calculating Deadline Timestamps
    • Sending the Initial Approval and Capturing the Approval ID
    • Architectural Pattern A: Send Approval Without Waiting, Poll Separately
    • Building the Do Until Loop
    • Step 1: Wait for approval response (short timeout)
    • Step 2: Check if approval came in
    • Step 3: Evaluate time thresholds
    • Crafting Teams Notifications That Actually Drive Action
    • Handling the Resolution Phase
    • Multi-Stage Approval Chains with Escalation
    • Persisting State for Resilience
    • Expression Reference for Time Comparisons
    • Hands-On Exercise
    • Common Mistakes & Troubleshooting
    • The Loop Runs Forever Despite Exit Conditions
    • Escalation Notifications Fire Multiple Times
    • Approval IDs Causing "Approval Not Found" Errors
    • Flow Fails After 30 Days on Long-Running Approvals
    • Teams Messages Going to the Wrong Person
    • Summary & Next Steps