---
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
-- 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
-- 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
list queues;
describe queue Ops.OrderProcessing;

create queue Ops.OrderProcessing ( Parallelism: 3, ClusterWide: true );
create queue Ops.Mail;                     -- defaults: parallelism 1, per-instance

create or modify queue Ops.OrderProcessing ( Parallelism: '$MyModule.Workers' );
drop 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
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.

### Two traps, both verified on mxbuild 11.13.0

**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
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 replace|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
create scheduled event Ops.SE_Nightly folder 'Private/Scheduled events'
  ( Microflow: Ops.ACT_Nightly, Repeat: Day, StartDateTime: '2026-01-01T02:00:00Z' );

create 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
`show 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 `show callers`, `GRAPH_DEAD_ASSETS` and lint rule QUAL004.

## Task queues (LIST/DESCRIBE/CREATE [OR MODIFY]/DROP 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
