One scheduled Power Automate flow, many dynamically configurable recurrences
Managing Power Automate flows that run on a schedule can become complex, especially when users have different scheduling preferences. But what if there was a way to simplify this process?
There is at least one scheduled Power Automate flow in almost every Power Platform solution that's responsible for sending emails or notifications on a scheduled basis. However, things can get complicated when different user groups have conflicting requirements. Some users might want to receive a notification every morning, while others prefer getting it on a weekly basis. To add to the complexity, your users may be spread across various time zones—making the concept of “morning” a relative one.
You might be tempted to create multiple flows with different schedules to meet the various time-based requirements of your users. However, you'll soon need to figure out the best strategy for reusing the same logic to execute certain actions, which likely means using child flows. As unique requirements continue to pile up, it can quickly become unmanageable. Eventually, you'll end up with dozens of scheduled flows that essentially perform the same task—executing a set of actions at specific times.
An alternative solution I propose is to use just one scheduled flow, triggered anywhere from multiple times an hour to once a day, depending on how frequently the task needs to be performed. That is a single trigger in one flow. The examples in this blog post will focus on executions up to four times per hour. To achieve this, you can split the hour into four 15-minute periods.
Configure the dynamic scheduling
Now that we have the basic understanding, let's dive into how you can configure dynamic scheduling to accommodate diverse user needs.
You’ll need to create a Dataverse table to store information about individual user groups and their scheduling preferences. It might look like this:
The most important column in the table is Schedule, which contains a cron expression. A cron expression is a string pattern used to define specific times for actions to occur, allowing you to schedule tasks flexibly and precisely based on your desired frequency. It consists of five key components: minute, hour, day of the month, month, and day of the week. Each component works together to define the precise schedule for executing a task.
The other columns shown are just examples and should be customized based on your use case. Each record in the table is linked to a specific Business Unit, allowing you to configure one or more schedules per unit. The Receiver column points to a Team that will receive the notifications, but this is just one possible implementation.
To define and understand cron expressions, you can use https://crontab.guru. If you want to allow users to make changes to the cron expression table themselves, you'll need to implement proper validation in the corresponding form or application.
The scheduled flow
The flow is scheduled to trigger once every 15 minutes. The key here is that the flow will execute 96 times a day; however, whether a particular run performs an action (such as sending a notification, an email, or executing another task) depends on the cron expression stored in the Schedules table and the current time provided by the trigger.
While this setup works seamlessly most of the time, there are occasional timing quirks to be aware of, especially when the platform is under heavy load.
The actual time of the trigger is not always deterministic, based on my research. You can control its behavior by setting the 'Start time' value so that both the hour and minute parts are zero. These components are used to calculate future run times. By ensuring they are zero, you guarantee that each trigger is scheduled precisely at 19:30:00, rather than at an arbitrary time like 19:30:37, which could result from the first recurrence.
The exact time of the trigger isn't always deterministic, based on my research. You can control its behavior by setting the 'Start time' value so that both the hour and minute are set to zero. These components are used to calculate future run times. By ensuring both are zero, you guarantee that each trigger is scheduled precisely at 19:30:00, rather than at an arbitrary time like 19:30:37, which could result from the first recurrence if a start time is not set.
It works properly most of the time, but I’ve seen complaints online about triggers being delayed. This may happen if the platform is under heavy load. This is a crucial detail when evaluating a cron expression against the current time. That’s why I recommend allowing some flexibility in your cron expression by specifying a range for the minutes component, such as 0-14 * * * *
, instead of just 0 * * * *
. The trigger might be slightly late, but it shouldn’t fire early. For example, if it's scheduled for 19:30 but triggers at 19:31:02, the cron expression will still match the current time.
If you have scheduled the flow four times an hour—at 0, 15, 30, and 45 minutes—the ranges for the minutes component can be 0-14, 15-29, 30-44, and 45-59, respectively. Here are a few examples:
- Once an hour:
0-14 * * * *
- Twice an hour:
0-14,30-44 * * * *
- Every day at 8:00 AM UTC:
0-14 8 * * *
- Every 1st day of the month at 8:30 AM UTC:
30-44 8 1 * *
Evaluating the cron expression
Now, you might wonder—how do we actually evaluate a cron expression in Power Automate? Let’s break that down. Unfortunately, there isn’t a built-in action for working with cron expressions. I recall there were some third-party actions available in the past, but I can't seem to find them anymore. In general, you should be cautious when using third-party extensions, primarily due to security and support concerns.
The natural solution to this is Azure Functions. This is why the HTTP connector above calls some-function.azurewebsites.net
. Here’s the implementation:
[Function("EvaluateCron")]
public IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req)
{
var cronExpression = req.Query["cron"];
var currentTimeString = req.Query["currentTime"];
if (string.IsNullOrWhiteSpace(cronExpression) || string.IsNullOrWhiteSpace(currentTimeString))
{
return new BadRequestObjectResult("Please provide both cron expression and current time.");
}
if (!DateTime.TryParse(currentTimeString, null, DateTimeStyles.AdjustToUniversal, out DateTime currentTime))
{
return new BadRequestObjectResult("Invalid current time format.");
}
// Adjust the current time to the start of the minute
var currentTimeAtZeroSeconds = currentTime.AddSeconds(-currentTime.Second);
var previousMinute = currentTimeAtZeroSeconds.AddSeconds(-1);
var schedule = CrontabSchedule.Parse(cronExpression);
var nextOccurrence = schedule.GetNextOccurrence(previousMinute);
bool isMatch = nextOccurrence == currentTimeAtZeroSeconds;
return new OkObjectResult(isMatch);
}
The function takes two query parameters: cron
and currentTime
, where currentTime
represents the scheduled trigger time. It adjusts currentTime
by removing any seconds and stepping back by one second. This ensures that the next occurrence of the cron expression, relative to the provided time, aligns exactly with the same minute as currentTime
.
Visit my Code-Samples repo to find the full source code for the function. If you need assistance with deploying it to Azure, refer to the official documentation.
By using a single scheduled Power Automate flow with configurable dynamic recurrences, you can simplify scheduling to meet diverse user needs while keeping your environment clean. However, there are a few things to keep in mind, such as potential delays and evaluating cron expressions.
I hope this guide helps you streamline your Power Automate flows. Feel free to share your thoughts or questions in the comments!