--- name: scheduled-events-and-queues description: "Scheduled events (Mendix's cron) and task queues in MDL — the eight repeat variants and the fields each takes, and what a queue does and does not throttle. Use when running a microflow on a schedule, or bounding how many background tasks run at once." --- # Scheduled Events and Task Queues ## When to Use This Skill Use this skill when the user wants to: - Run a microflow on a schedule ("every night at 4", "hourly", "cron", "batch job") - Inspect or change an existing scheduled event - Limit how many background tasks run at once (a task queue) - Run a microflow or Java action call on a queue (`IN QUEUE`) **These two features are unrelated.** A scheduled event does **not** go through a task queue. Its own concurrency control is `OnOverlap`. ## Scheduled Events Mendix's cron: run a microflow on a repeating schedule. ```sql mdl 1; -- Inspect list scheduled events; list scheduled events in Ops; describe scheduled event Ops.NightlyCleanup; -- re-executable MDL -- Create create scheduled event Ops.NightlyCleanup ( Microflow: Ops.SE_Cleanup, Repeat: Daily, HourOfDay: 4, MinuteOfHour: 0, TimeZone: Server, Enabled: true ); drop scheduled event Ops.NightlyCleanup; ``` `Microflow` and `Repeat` are **always required**. `show` is a synonym for `list`. ### Pick the Repeat first, then use only its fields Mendix stores the repeat rule as one of eight types, and they differ in **which fields they carry** — not just in their values. Naming a field from another repeat is an error, not a no-op: ``` Error: Repeat Daily does not have Multiplier — it takes HourOfDay, MinuteOfHour ``` | Repeat | Fields | Means | |--------|--------|-------| | `Minutely` | `Multiplier` | every N minutes | | `Hourly` | `Multiplier`, `MinuteOffset` | every N hours, at :MM past | | `Daily` | `HourOfDay`, `MinuteOfHour` | every day at HH:MM (**no multiplier**) | | `Weekly` | `Weekdays`, `HourOfDay`, `MinuteOfHour` | on the named days at HH:MM | | `MonthlyByDate` | `Multiplier`, `MonthOffset`, `DayOfMonth`, `HourOfDay`, `MinuteOfHour` | the Dth of every N months | | `MonthlyByWeekday` | `Multiplier`, `MonthOffset`, `DaySelector`, `Weekday`, `HourOfDay`, `MinuteOfHour` | the last Friday of every N months | | `YearlyByDate` | `Month`, `DayOfMonth`, `HourOfDay`, `MinuteOfHour` | every 2 January | | `YearlyByWeekday` | `Month`, `DaySelector`, `Weekday`, `HourOfDay`, `MinuteOfHour` | the first Monday of March | Field values: | Field | Value | |-------|-------| | `Multiplier` | 1 or more (defaults to 1) | | `MinuteOffset` | 0–59 | | `MonthOffset` | 0-based: which month of a multi-month cycle fires | | `HourOfDay` / `MinuteOfHour` | 0–23 / 0–59 | | `DayOfMonth` / `Month` | 1–31 / 1–12 | | `Weekdays` | quoted list: `'Monday, Friday'` (case-insensitive) | | `DaySelector` | `First`, `Second`, `Third`, `Fourth`, `Last` | | `Weekday` | `Sunday` … `Saturday` | Optional on any repeat: | Property | Values | Default | |----------|--------|---------| | `Enabled` | `true` / `false` | `false` — **a new event does not run until you enable it** | | `OnOverlap` | `DelayNext` / `SkipNext` | `DelayNext` | | `TimeZone` | `UTC` / `Server` | `UTC` | | `StartDateTime` | RFC 3339, e.g. `'2026-01-01T04:00:00Z'` | none | | `Documentation` | free text | none | `SkipNext` drops a run that would overlap the previous one; `DelayNext` waits. ### More examples ```sql mdl 1; -- Every two hours, 23 minutes past create scheduled event Ops.HourlyPing ( Microflow: Ops.SE_Ping, Repeat: Hourly, Multiplier: 2, MinuteOffset: 23 ); -- Mondays and Fridays at 09:30 create scheduled event Ops.WeeklyReport ( Microflow: Ops.SE_Report, Repeat: Weekly, Weekdays: 'Monday, Friday', HourOfDay: 9, MinuteOfHour: 30 ); -- The last Friday of every third month, 18:00 create scheduled event Ops.QuarterEnd ( Microflow: Ops.SE_Close, Repeat: MonthlyByWeekday, Multiplier: 3, MonthOffset: 2, DaySelector: Last, Weekday: Friday, HourOfDay: 18 ); ``` ## Task Queues A task queue bounds how many queued calls run at once. Binding a call to it is a separate step — see [`IN QUEUE`](#binding-a-call-to-a-queue--in-queue). ```sql mdl 1; list task queues; describe task queue Ops.OrderProcessing; create task queue Ops.OrderProcessing ( Parallelism: 3, ClusterWide: true ); create task queue Ops.Mail; -- defaults: parallelism 1, per-instance create or modify task queue Ops.OrderProcessing ( Parallelism: '$MyModule.Workers' ); drop task queue Ops.Mail; ``` | Property | Meaning | Default | |----------|---------|---------| | `Parallelism` | how many run at once — an **expression**, not a number | `1` | | `ClusterWide` | `true` = across the cluster, `false` = per runtime instance | `false` | Mendix stores parallelism as an expression string, so `3` and `'3'` are the same thing and an arbitrary expression is legal. ## Common Mistakes | Mistake | Symptom | Fix | |---------|---------|-----| | `Multiplier` on a `Daily` repeat | `Repeat Daily does not have Multiplier` | Daily has no multiplier — use `HourOfDay`/`MinuteOfHour`, or switch to `Hourly` | | Forgetting `Enabled: true` | The event is in the model but never runs | Set `Enabled: true` (the default is false) | | `TimeZone: server` | `has the wrong casing — Mendix stores it as "Server"` | Use the exact spelling: `Server`, `UTC`, `DelayNext`, `SkipNext`, `Last`, `Friday` | | `HourOfDay: 24` | `it must be between 0 and 23` | Hours are 0–23; midnight is `0` | | Expecting a queue to throttle a scheduled event | Nothing changes | They are unrelated — use `OnOverlap` | ## Binding a Call to a Queue — `IN QUEUE` The queue document only *defines* the concurrency limit. What actually runs work in the background is the binding on the **call activity**, and Mendix allows it on exactly two: *Call microflow* and *Call Java action*. In MDL that is a trailing `in queue` clause, in the same position on both — after the argument list, before any `on error`: ```sql mdl 1; create or modify microflow Ops.ACT_Enqueue () begin call microflow Ops.ACT_Process(Order = $Order) in queue Ops.OrderProcessing; call java action Ops.RefreshData(Url = $Url) in queue Ops.OrderProcessing; end; ``` `describe microflow` renders the clause back, so the binding round-trips. ### Three traps, verified on mxbuild 11.13.0 (the microflow one on 11.12.0) **A queued microflow must return nothing.** A `call microflow … in queue …` whose target declares `returns …` fails the build with **CE7033** *"A microflow used for background execution must have a Microflow return type of 'Nothing'."*, reported at the call activity. `mxcli check` reports it as **MDL088** — without a project when the script creates the microflow, and under `--references` for one already stored. Drop the `returns` clause (and the `return` value) from the worker microflow: ```sql mdl 1; create microflow Ops.ACT_Work ($Note: String) -- no `returns` begin log info node 'Ops' 'working'; end; ``` **A queued Java action must return Nothing.** Anything else fails the build with **CE7038** *"A Java action used for background execution must have a return type of 'Nothing'."* mxcli's default return type for `create java action` is **Boolean**, so `returns void` is required, not optional: ```sql mdl 1; create java action Ops.RefreshData(Url: string not null) returns void as $$ return; $$; ``` **The queue must exist.** A missing one is **CE1613** *"The selected task queue … no longer exists"*, reported against the **call activity** — it names the activity, not the script, so a typo is expensive to trace from the build log. `mxcli check --references` resolves the name first and reports it against the statement instead. That CE1613 is also the proof the binding is real: drop the queue on a project mxcli wrote and the error appears, naming both the queue and the activity. ### A Rewrite Must Restate the Queue `create or modify microflow` rebuilds the microflow from the statement, so a binding the script does not restate is gone. mxcli refuses rather than drop it: ``` Error: microflow Ops.ACT_Caller has 1 call(s) bound to a task queue that this script does not restate (Ops.MyQueue), and rewriting it would silently drop the binding. ``` Add `in queue Ops.MyQueue` to the call and the rewrite goes through. Without the refusal the binding was written back as null and the project then looked *healthier* than before — `mx check` stopped reporting CE1613, because the configuration the error was about had been deleted. One thing is still refused: a **retry policy** on a queued call (`Queues$QueueFixedRetry` / `Queues$QueueExponentialRetry`). MDL has no syntax for it, so a rewrite cannot preserve it — change that microflow in Studio Pro. ### When a Java Action Is the Better Tool The activity property gives queueing and nothing else. The runtime API, reachable from a Java action, gives queueing *and* retry, and can queue a Java action directly with no wrapper microflow: ```java Core.userActionCall("Ops.RefreshData") .withParams(url) .withExponentialRetry(5, Duration.ofSeconds(2), Duration.ofMinutes(2)) .executeInBackground(ctx, "Ops.OrderProcessing"); ``` Use `Core.microflowCall(...)` when the unit of work really is a microflow. ## Placing Them in Folders Both take a `folder` clause on `create`, straight after the qualified name: ```mdl mdl 1; create scheduled event Ops.SE_Nightly folder 'Private/Scheduled events' ( Microflow: Ops.ACT_Nightly, Repeat: Daily, StartDateTime: '2026-01-01T02:00:00Z' ); create task queue Ops.Q_Imports folder 'Private/Queues' ( Parallelism: 3 ); ``` On `create or modify` the clause moves an existing document; omitting it leaves placement alone. See `organize-project`. ## Validation Checklist Before presenting a script: ```bash mxcli check script.mdl # catches wrong-repeat fields (MDL-SCHED01) mxcli check script.mdl -p app.mpr --references ``` - [ ] Every scheduled event has `Microflow` and `Repeat` - [ ] Only that repeat's fields are used - [ ] `Enabled: true` if it is meant to run - [ ] Enum values spelled exactly (`Server`, `DelayNext`, `Last`, `Monday`) - [ ] The target microflow exists and takes no parameters ## Querying and Linting Both document types are in the catalog after `refresh catalog`: ```sql -- Anything that fires more often than once a minute select QualifiedName, RepeatDescription, Microflow from CATALOG.SCHEDULED_EVENTS where Enabled = 1 and IntervalSeconds < 60; select QualifiedName, Parallelism, ClusterWide from CATALOG.QUEUES; ``` A scheduled event counts as a caller of the microflow it runs, so `list callers of Ops.SE_Cleanup` lists it and the lint rule for orphaned microflows (QUAL004) does not flag it. `IntervalSeconds` is derived from the schedule, not from the legacy `Interval`/`IntervalType` pair Mendix also stores. Starlark lint rules can iterate both: `scheduled_events()` yields `repeat`, `interval_seconds`, `on_overlap`, `time_zone`, `enabled`, and `microflow_name`; `queues()` yields `parallelism` (a string) and `cluster_wide`. ## Related - `mxcli syntax scheduled-event`, `mxcli syntax queue` — full syntax reference - `write-microflows` — writing the microflow the event calls - `project-settings` — after-startup / before-shutdown microflows ## Scheduled events — Mendix's cron (LIST/DESCRIBE/CREATE [OR MODIFY]/DROP). `Repeat:` names one of the eight `ScheduledEvents$*Schedule` variants and only that variant's fields are accepted; a field from another repeat is refused by `mxcli check` (MDL-SCHED01) and by exec, which call the same function. The document shape is pinned by re-serializing three whole Studio Pro-authored events (Workflow Commons 4.11.0, OIDC SSO 4.6.0, SAML 4.2.1) element by element — `modelsdk/gen` is **wrong** about two properties here the integers are stored as int64 (gen says int32, the #585 mismatch) and `StartDateTime` is a BSON datetime (gen says string), so both engines share one raw-BSON codec in `mdl/scheduledevents`. `Interval`/`IntervalType` are legacy siblings of `Schedule` that Studio Pro writes and does not keep in sync — derived on CREATE, carried through untouched on MODIFY. Only the Day and Hour variants have a Studio Pro reference; the other six are metamodel-derived and verified to load. Both are in the catalog (`CATALOG.SCHEDULED_EVENTS`, `CATALOG.QUEUES`) and a scheduled event emits a `schedule` edge into `CATALOG.REFS` — without it a microflow run only by a scheduled event was reported as dead by `list callers`, `GRAPH_DEAD_ASSETS` and lint rule QUAL004. ## Task queues (LIST/DESCRIBE/CREATE [OR MODIFY]/DROP TASK QUEUE). `Config.ParallelismExpression` is a **string** and the sibling int32 `Parallelism` is not written — matching all four Studio Pro queues in Business Events 3.12.1. Binding a *call* to a queue is not yet authorable, so `CREATE OR REPLACE|MODIFY MICROFLOW` is **refused** when the stored microflow has a queued call (guard-don't-drop, ADR-0005) the rebuild used to write `QueueSettings` back as null, which made `mx check` go from CE1613 to 0 errors by deleting the user's configuration