---
title: "Power Automate from Scratch, Part 2: Approvals and escalation, building flows people actually respond to"
date: 2026-08-13T00:00:00Z
updated: 2026-09-11T11:47:29Z
tags: ["Power Automate", "Automation", "Governance", "Microsoft 365"]
canonical: https://bianca.codes/blog/power-automate-from-scratch-part-2-approvals-and-escalation-building-flows-people-actually-respond-to/
---

# Power Automate from Scratch, Part 2: Approvals and escalation, building flows people actually respond to

_The mechanics behind a Power Automate approval that actually reminds and escalates: why the reminder branch has to start before the approval, and how Configure run after decides what happens next._

← Previously: [Part 1: Triggers and connectors, the mental model nobody explains](/blog/power-automate-from-scratch-part-1-triggers-and-connectors-the-mental-model-nobody-explains/) (6 Aug) → Next: [Part 3: Error handling and retry policies, failing loudly vs failing silently for three weeks](/blog/power-automate-from-scratch-part-3-error-handling-and-retry-policies-failing-loudly-vs-failing-silently-for-three-weeks/) (20 Aug)

**What this post covers**: why Start and wait for an approval blocks the flow rather than polling for a response; why the reminder branch has to split off before the approval action, not after it; how Configure run after turns a timeout into an escalation instead of a silent dead end; and the AND/OR rule that decides whether your escalation branch actually fires when you expect it to.

## An approval is a blocking action, and that's the whole trick

Most people's first mental model of Start and wait for an approval is that it's checking in periodically, the way a trigger polls a mailbox. It isn't. When that action runs, the flow instance pauses at that exact step. Nothing after it executes, nothing before it can be re-run, and the run sits there, technically "running", for as long as it takes a human to click a button or until the timeout you set expires.

That's a deliberate design choice, not a limitation. Every output the action produces, who responded, what they picked, any comments they left, only exists once someone has actually responded. Blocking is what lets the next action in your flow trust that data without a null check. The trade-off is that a blocked run occupies a run slot the whole time it's waiting, which is fine for one approval and worth knowing about if you're chaining several of them back to back for a sequential sign-off.

[Build an approval flow with a reminder and a 48-hour escalation](/blog/build-an-approval-flow-with-a-reminder-and-a-48-hour-escalation/) already walked through the exact click path for this shape. This post is the part that post skipped on purpose: why each piece has to sit where it sits.

## The reminder has to start before the approval, not after

This is the mistake almost everyone makes on their first attempt, and it comes directly from not knowing the action blocks. The instinct is to put the reminder logic after Start and wait for an approval, reasoning "if nobody's responded by then, send a nudge." That branch never runs early, because nothing after a blocking action runs until the block clears. By the time your reminder step executes, the approval is already resolved and the reminder is either late or pointless.

The fix is a parallel branch that splits off _before_ the approval action starts, not after it:

```plaintext
Trigger
 ├─ Branch A: Start and wait for an approval (Timeout: PT48H)
 └─ Branch B: Delay (PT24H) → Condition: has the approval already resolved? → if no, send reminder
```

Branch B needs a way to check "has this already been answered" without touching the approval action directly, since it can't read an output that doesn't exist yet on a parallel path. The usual pattern is a boolean variable, initialised false before the split, set true by an action placed right after the approval resolves. Branch B's delay wakes up, checks the variable, and only sends the reminder if it's still false. Skip the variable and you'll get a reminder email sent to someone who approved the request twenty minutes ago, which is its own kind of trust-eroding.

## Escalation lives in Configure run after, not in the approval action

The approval action's own Settings has a Timeout field, entered as an ISO 8601 duration. `PT48H` for 48 hours, `P14D` for two weeks, `P2W` also works if you'd rather write it that way. That field only controls when the action gives up waiting. It does not, on its own, do anything with that outcome. The escalation happens in a separate action below it, configured to run specifically when the approval times out.

That configuration lives under the ellipsis menu on the action you want to escalate to: Configure run after, then tick "has timed out" and untick "is successful". Left on the default, that action only fires after a successful response, which is why a lot of first-pass escalation branches silently never trigger, they're configured to run after success, not after the specific failure mode of nobody answering.

### The AND/OR rule that trips people building this

Configure run after behaves differently depending on how many actions feed into the one you're configuring. Tick multiple outcomes on a single action's run-after settings, "has failed" and "has timed out" together, and that's an OR: either outcome fires it. But when an action sits downstream of more than one branch, each with its own run-after condition, Power Automate requires every one of those upstream conditions to be satisfied before the downstream action runs. That's an AND across branches, not an OR.

This matters the moment you try to make the escalation branch also wait on the reminder branch finishing cleanly. Configure the escalation step to run after both "the approval has timed out" and "the reminder branch is successful", and you've just built a flow that never escalates if the reminder branch, for whatever reason, doesn't resolve as a clean success. Keep the branches independent unless you genuinely need that dependency, and know which rule you're invoking before you tick a second box.

## The clean-up step is where the licensing catches you

Approvals itself is a standard connector. Nobody responding to a request needs a premium licence, and the seeded M365 licence most people already have covers it. Where the edges of that show up is in what you do _after_ the timeout, specifically if you try to delete the now-stale approval record so the original approver can't respond to a request the flow has already moved past. Deleting that Dataverse row is a premium action. If your environment doesn't have Dataverse premium capacity, that record just sits there, answerable, even after escalation.

The honest fix isn't a licence upgrade for a cleanup step. It's a status field on the record itself, set by whichever branch resolves first, so a late response to the original approval gets checked against a status that already says "escalated" and gets ignored downstream rather than trusted. The flow can't stop someone opening a stale email. It can stop that stale response from mattering.

## Frequently Asked Questions

### **Does the reminder branch need Dataverse, or does a plain variable work?**

A plain boolean flow variable is enough. Dataverse only becomes relevant if you're already tracking the request as a row there for other reasons, and even then a simple status column does the same job as a variable, just persisted outside the run.

### **What if I want more than one reminder before escalating?**

Add more delay-and-check cycles on the same parallel branch, each checking the same guard variable, rather than nesting a second approval action. One Start and wait for an approval per request is enough. The reminders are just repeated pings against the same pending state.

### **Does Configure run after work the same way on Power Automate desktop flows?**

No. Desktop flows run a linear script and use their own error-handling blocks (On Block Error) rather than the cloud flow's per-action run-after settings. This post is about cloud flows specifically.

### **Can the approval action wait for more than one person before resolving?**

Yes, and it's a setting on the action itself, not something you build with extra actions. "Approve/Reject, Everyone must approve" blocks until every assigned approver responds; "Approve/Reject, First to respond" resolves the moment any one of them does. Pick the wrong one and either your escalation fires too early or a request sits open long after the person who mattered already signed off.

### **Why not just use a Condition action instead of Configure run after?**

A Condition checks a value that already exists, it can't observe an action's own success, failure, skip, or timeout state directly without you first capturing that into a variable yourself. Configure run after reads the action's actual outcome natively, which is both less code and more reliable, since you're not re-deriving state Power Automate already tracked for you.

### **Does the 48-hour timeout count weekends and after-hours?**

Yes. The timeout is a wall-clock duration from when the action starts, with no business-hours awareness built in. A request sent Friday afternoon with a 48-hour timeout escalates Sunday afternoon, not the next business day. If that's not what you want, the timeout duration needs to be padded manually, or the trigger needs its own day-of-week guard.

## Where the series goes from here

[Part 3: Error handling and retry policies, failing loudly vs failing silently for three weeks](/blog/power-automate-from-scratch-part-3-error-handling-and-retry-policies-failing-loudly-vs-failing-silently-for-three-weeks/) (20 Aug) takes the same Configure run after mechanism from this post and points it at a different problem: what happens when an action fails outright instead of timing out, and how a flow that's failing quietly for weeks looks identical, from the outside, to one that's working fine.

_Part 3 lands 20 Aug._
