Post

#PowerPlatformTip 171: 'Debounce the Email Trigger with a Status Flag & Modified-Date Window'

#PowerPlatformTip 171: 'Debounce the Email Trigger with a Status Flag & Modified-Date Window'

TL;DR: Let the flow run in parallel, but guard it with a single SharePoint control row. Each run checks a Yes/No IsRunning flag. If it’s Yes, do nothing. If it’s No, read the stored WindowStart, remember the current time as RunStart, set the flag to Yes, then Get emails (V3) + Filter array to collect every mail since WindowStart, process the batch, and finally write RunStart back as the new WindowStart and set the flag to No.

When mails arrive in bursts, the When a new email arrives (V3) trigger spins up many parallel runs. Instead of throttling them, you let the first run become the collector: it reads the time window from a stored WindowStart value on the control item, processes everything since then, and every other run simply exits because the flag is already Yes.

💡 Challenge

On a busy inbox, a short burst of mails makes the trigger start many flow instances at once. That means duplicated work, connector throttling, inconsistent state, and sometimes runs that look skipped. You want the mails processed once, as a batch, and without losing mails that arrived while a run was already busy.

✅ Solution

Keep a status flag in a tiny SharePoint control list (one row, a Yes/No column IsRunning). The flow runs in parallel as normal, but every run first checks the flag:

  • Flag = Yes → another run already owns the batch → do nothing.
  • Flag = No → this run is the collector. It reads the row’s WindowStart (a Date and time column), notes the current time as RunStart, flips the flag to Yes, then fetches all mails received since WindowStart and processes them. Finally it writes RunStart into WindowStart and resets the flag to No.

Why a dedicated WindowStart column and not the item’s Modified date: Modified changes on every update, including the final reset. A mail that arrives while the batch is running would then fall before the next window start and never be picked up. With RunStart (taken before fetching), the next window begins exactly where this fetch began, so such mails are included next time.

flowchart TD
    A["New email arrives<br/>(parallel runs)"] --> B["Get item: control row"]
    B --> C["Read WindowStart, set RunStart = utcNow()"]
    C --> D{"IsRunning = Yes?"}
    D -->|Yes| E["Terminate<br/>another run owns this batch"]
    D -->|No| F["Update item: IsRunning = Yes"]
    F --> G["Get emails since WindowStart"]
    G --> H["Filter array by receivedDateTime"]
    H --> I["Apply to each: process + mark read"]
    I --> J["Update item: IsRunning = No, WindowStart = RunStart"]

🔧 How It’s Done

1. Create the control list.

🔸 A SharePoint list FlowLocks with a single item, e.g. Title = EmailProcessor, a Yes/No column IsRunning (default No) and a Date and time column WindowStart (set it to a moment before the first mail you want to process). Note its item ID.

2. Leave the trigger parallel.

🔸 Keep When a new email arrives (V3) as is — no concurrency change. Parallelism is fine; the flag does the gating.

3. Read the control item and take the start time of this run.

🔸 Get item on FlowLocks (your control row ID).

🔸 Store its WindowStart in a variable WindowStart:

1
body('Get_item')?['WindowStart']

🔸 Store the current time in a variable RunStart:

1
utcNow()

⚠️ Take RunStart before you fetch any mail, otherwise a mail arriving in between can fall out of every window.

4. Condition: is it already running?

🔸 Condition: IsRunning is equal to true.

5. If Yes → do nothing.

🔸 If yes branch → Terminate with status Succeeded. Another run owns this batch.

6. If No → claim it and set the flag.

🔸 If no branch → Update item on FlowLocks → IsRunning = Yes.

🔸 Do not touch WindowStart yet. It is only moved forward after the batch succeeded, so a failed run is retried over the same window.

7. Collect every mail in the window.

🔸 Get emails (V3) — Fetch Only Unread = Yes, raise Top to cover the burst (the action returns at most that many mails per call, so choose a value above your expected burst size). Results include a receivedDateTime property.

🔸 Filter array on the returned value, keep only mails since the window start:

1
@greaterOrEquals(item()?['receivedDateTime'], variables('WindowStart'))

8. Process the batch.

🔸 Apply to each over the filtered array → your work (save, notify, create items…).

🔸 Mark as read or unread (V3) on each MessageId so it isn’t picked up again.

9. Release the flag — always.

🔸 Update item on FlowLocks → IsRunning = No and WindowStart = RunStart.

🔸 If the batch failed, only reset IsRunning and leave WindowStart unchanged (use a Condition on the result of the processing scope), so the next run covers the same window again.

🔸 Open Configure run after on this action and also run it after has failed / has timed out, so an error never leaves the flag stuck on Yes.

🎉 Result

One collector run absorbs the burst: it processes every mail from the stored WindowStart up to its own start, while the parallel duplicate runs exit instantly. Less stampede and less throttling. Mails that arrive during the batch are picked up by the next run, which is triggered by the next incoming mail (see Q6).

🌟 Key Advantages

🔸 Runs in parallel, still safe: the flag gates the work, no irreversible concurrency setting needed.

🔸 No gaps between windows: each window starts where the previous fetch began, because WindowStart is set from RunStart.

🔸 Standard connectors only: Office 365 Outlook + SharePoint, no premium.

🔸 Self-healing flag: Configure run after resets it even on failure.

🛠️ FAQ

1. Why a WindowStart column instead of the item’s Modified date?

Every update of the item changes Modified, including the reset at the end of a run. A window based on Modified would therefore start after any mail that arrived during the batch, and that mail would never be processed. A dedicated WindowStart column that you only move forward from RunStart avoids this.

2. With parallel runs, can two runs both read No at the same time?

Yes. Reading the flag and setting it are two separate steps, and SharePoint has no atomic check-and-set, so this pattern is not a hard lock. Two runs can both claim the batch. Because Fetch Only Unread and Mark as read are used, a duplicate run mostly finds nothing left, but duplicate processing is possible. If you need strict exclusion, use trigger Concurrency Control = 1 (see #35).

3. What if a run fails after setting the flag to Yes?

Use Configure run after on the final Update item so it also runs on failed/timed out. That resets IsRunning to No and the next mail starts a clean cycle.

4. Get emails (V3) has no “received after” filter — how does the window work?

Right, there’s no native date parameter. The action returns receivedDateTime per mail, so a Filter array with greaterOrEquals(..., WindowStart) does the time-window filtering client-side.

5. Can I store the flag in Dataverse instead?

Yes, any store with a single control record and a timestamp column works. SharePoint is the lightest option.

6. What about a mail that arrives while the batch runs and whose own run exited on the flag?

Its run exits, so nobody processes it until the next trigger fires. If that is not acceptable, add a scheduled flow (e.g. every 15 minutes) that runs the same collector logic as a safety net.

This tip is by Marcel Lehmann, Microsoft MVP (Business Applications) and founder of the Power Platform Academy – live Power Platform workshops, free or CHF 49. Join the workshop waitlist to get notified about new dates. For companies that want this implemented for them, see KMUpower – Power Platform consulting & automation for SMEs (DACH).

This post is licensed under CC BY 4.0 by the author.

Microsoft and the Power Platform product names (for example Power Apps, Power Automate, Copilot Studio) are trademarks of the Microsoft group of companies. This is an independent website. It is not operated, sponsored or endorsed by Microsoft.