Flow Narration ·

An approval flow, narrated: why each step exists, and where it breaks

Almost every environment I've worked in has some version of this flow. An expense claim, a leave request, a purchase order: someone submits something, an amount or a role decides who has to approve it, and if that person goes quiet, nothing happens until someone notices. It's the flow every organisation eventually builds, and it's rarely documented past the day it shipped.

This isn't one client's actual flow. It's a composite, built from the expense-approval shape I keep rebuilding in different environments: a threshold branch, a manager approval, an escalation nobody remembers is there. If your flow does something differently, that difference is usually worth writing down too.

The flow, end to end

An expense claim comes in. If it's under a set amount, it goes straight to the submitter's manager. Over that amount, it goes to the finance director instead. Whoever approves it triggers an update and a notification. Whoever doesn't respond within 48 hours triggers a reminder, not a decision, because auto-approving a stale request is a worse failure mode than a slow one.

Expense claim approval, simplified
Flowchart: expense claim approval with threshold and escalation A claim is submitted. If the amount is over the threshold it goes to the finance director, otherwise to the manager. Either approver has 48 hours to respond before a reminder fires. Approval updates the record and notifies the submitter; rejection does the same with a reason. Claim submitted Over threshold? yes Finance approves no Manager approves Update record, notify
View as Mermaid (copyable)
flowchart LR
    A[Claim submitted] --> B{Over threshold?}
    B -->|yes| C[Finance director approves]
    B -->|no| D[Manager approves]
    C --> E{Response within 48h?}
    D --> E
    E -->|no| F[Send reminder to approver]
    F --> E
    E -->|approved| G[Update record, notify submitter]
    E -->|rejected| H[Update record, notify with reason]

Power Automate ships with a gallery of ready-made approval templates, and Microsoft's own guidance on getting started with approvals lists using one of these as the first option, before building from scratch. They get you a working approval fast. What they don't typically include is a threshold branch, an escalation running alongside the approval, or a check for whether the approver's actually around, and that gap is what this post is actually about.

Step by step: what each part does and why

1. The trigger

The trigger is Dataverse's When a row is added, modified, or deleted trigger, configured for Added only, on the Expense Claims table. This matters more than it looks: triggering off the record rather than a form submission means anything that creates a valid claim (a mobile app, a bulk import, a future integration) starts the same approval, instead of the logic living inside one form.

2. The threshold condition

One condition checks the claim amount against a fixed value. The value itself isn't the interesting part; the reason it exists is: below it, a manager has enough context and authority to decide alone. Above it, the org wants a second, more senior set of eyes. Whenever you see a threshold like this in a flow, ask what decision it's actually encoding. It's rarely just "money."

3. The approval action, assigned conditionally

Both branches use the same Approvals action, just with a different assignee. Keeping the approval step identical across branches (instead of duplicating separate approval logic per branch) is deliberate. When both branches later need a third field on the approval card, you change it once.

4. Checking before you assign, not after

The approval and escalation steps as built so far are honest but passive: assign the approval, start a clock, react if nobody answers. A version that's had a few more iterations checks something first, before the approval task even gets created: is the person about to be assigned actually around.

Two Office 365 actions do the actual work. Office 365 Outlook → Get mail tips for a mailbox (V2) returns the intended approver's automatic reply message, if one's currently set. Office 365 Users → Get manager (V2) resolves that person's manager dynamically, instead of a second hardcoded name sitting next to the first one.

The logic sits between step 3 and the approval task itself: work out who the approver should be (finance director or manager, from the threshold branch), pull their mail tips, and if an automatic reply message comes back, assign the approval to their manager instead, with a note on the approval card explaining why it was rerouted so nobody's confused when the wrong name shows up.

It's worth being as honest about the limits of this as the rest of the flow. It only catches people who've actually turned an auto-reply on, which is far from everyone. It doesn't help if the manager is also out. And it's an extra connector call on every single approval: a fine trade for something this frequent, but worth knowing you're paying it. A stored "backup approver" field that someone maintains deliberately is more reliable than inferring availability from a mailbox setting, slower to build, but it doesn't depend on people remembering to turn something on before they leave.

Checking before you assign, step 4
Flowchart: checking automatic replies before assigning the approval Once the approver is identified, the flow checks their mail tips for an automatic reply message. If one's set, the approval is assigned to their manager instead, with a note. Otherwise it's assigned as planned. Approver identified Auto-reply on? yes Reroute to their manager no Assign as planned
View as Mermaid (copyable)
flowchart LR
    A[Approver identified] --> B{Auto-reply on?}
    B -->|yes| C[Reroute to their manager, with a note]
    B -->|no| D[Assign as planned]

5. The escalation, running in parallel

This is the part people build second, if they build it at all. A delay runs alongside the approval wait. If the delay finishes first, the flow sends a reminder rather than a decision, and restarts the wait. This is a race-condition pattern, not a branch off the approval: the approval task and the delay run at the same time, and whichever finishes first decides what happens next.

Auto-approving after a timeout is tempting because it "unblocks" things, but it quietly moves the approval authority from a person to a clock. A reminder keeps the decision where it belongs and just makes silence louder.

The escalation, step 5: a race, not a branch
Flowchart: the approval wait and the 48-hour delay run at the same time When the approval is assigned, the approval task and a 48-hour delay start together. If the approver responds first, the outcome is recorded. If the delay finishes first with no response, a reminder is sent and the wait restarts. Approval assigned Approver responds first Delay finishes first Outcome recorded Send reminder restarts the wait
View as Mermaid (copyable)
flowchart TD
    A[Approval assigned] --> B[Approver responds first]
    A --> C[Delay finishes first]
    B --> D[Outcome recorded]
    C --> E[Send reminder]
    E --> A

6. Outcome handling

Approved and rejected both do the same two things: update the Dataverse row's status field, and notify the submitter. The only difference is the message and whether a rejection reason gets attached. Two near-identical branches doing almost the same thing is a normal, honest shape for a flow. Don't over-engineer it into one clever branch just to avoid repetition.

Where flows like this quietly break

None of these are exotic failures. They're the ordinary ways an approval flow drifts from what it was supposed to guarantee, and they're exactly the kind of thing that's obvious in five minutes to whoever built the flow and invisible to whoever inherits it.

If you inherited this flow

Before you read the list below, it's worth answering it yourself first: if you inherited this flow tomorrow, what's the first thing you'd check? Hold that answer, then see how it compares.

Five things worth checking in the first sitting, before you change anything: who's hardcoded as an approver and whether they still hold that role, whether the threshold value lives in the flow or somewhere it can be changed without editing logic, what happens after the second and third reminder (not just the first), whether rejection actually captures a reason, and whether the record's status can drift from the approval history. Every one of those is a five-minute check that saves someone a bad afternoon later.

← Back to FlowNarrate