English
Loop
Repeats a set of nodes for each element of a list: each attachment, each row, each recipient.
The Loop node repeats a set of nodes for each element of a list: each attachment, each table row, each recipient. The nodes connected to item form the body; they run once per element, each time in a separate run with its own retries and idempotency. The rest of the workflow, connected to done, runs once, when all iterations have concluded, and can read the aggregated results.
Inside the body, the current element is {{ data.item }}, along with {{ data.index }}, {{ data.count }}, {{ data.first }} and {{ data.last }}. The body also sees everything produced before the loop ({{ data.<earlier step>.… }}, {{ email.… }}). In nested loops, data.item is always the innermost element; reach the outer one through the outer loop's name, {{ data.<outer loop>.item }}.
The list usually comes from an expression that resolves to an array, such as {{ email.attachments }} or {{ data.read.rows }}. With "A hand-written list", type one value per line or separate them with commas.
At a glance
- Type:
flow.loop· version 1 - Category: Logic
- Kind: Step — one stage of a run
- Effect: No external effect (
none) — nothing is written outside Mankomail; safe to replay - Needs a carrier email: No
- Connection: None
- Inputs:
main - Outputs:
item,done
Parameters
source
The list comes from
- Type: One choice (
options) - Required: Yes
- Default:
expression - Options:
expression— An expression: The normal case: the output of an earlier node, for example{{ data.list_1.rows }}or{{ email.attachments }}.lines— A hand-written list: One value per line (or comma-separated). For the three values you type yourself.
items
The items — The list to walk. Each element becomes {{ data.item }} inside the loop body, along with {{ data.index }} and {{ data.count }}.
- Type: Long text (
text) - Required: Yes
- Default:
""(empty) - 20000 characters at most
- Example:
{{ email.attachments }} - Expressions:
{{ }}accepted
batchSize
Items per iteration — Leave at 1 to handle one item at a time. Above that, {{ data.item }} is an array of N items.
- Type: Number (
number) - Required: No
- Default:
1 - Whole number, from 1 to 100
concurrency
Iterations in parallel — Sequential by default (1): the order is preserved. Above that, iterations overlap and the execution order is no longer guaranteed.
- Type: Number (
number) - Required: No
- Default:
1 - Whole number, from 1 to 5
- Shown under “Advanced” in the editor
onItemError
If an iteration fails
- Type: One choice (
options) - Required: Yes
- Default:
stop - Options:
stop— Stop the loop: No further iteration is started, and the loop fails — the node error policy then applies.continue— Carry on and collect the error: The loop runs to the end; failures are counted and listed in the step data.
maxIterations
Maximum number of iterations — A guard rail: a longer list fails the loop instead of silently handling only part of it.
- Type: Number (
number) - Required: No
- Default:
100 - Whole number, from 1 to 500
- Shown under “Advanced” in the editor
maxDurationMs
Time budget — Beyond it, the loop concludes with what it has done. Iterations already started do run to completion.
- Type: Duration (
duration) - Required: No
- Default: 1 hour (
3600000) - Stored in milliseconds, typed in minutes or hours
- From 1 minute to 24 hours
- Shown under “Advanced” in the editor
Outputs
item— The loop body. What is connected here runs once per item (or per batch), each time in its own run. It is not the continuation of the node.done— Taken once, when every iteration has concluded or the time budget has elapsed. Also taken immediately when the list is empty.
Data produced
What this node adds to the run data, and how to read it in an expression. <step> stands for the step key: the node name turned into an identifier (see Data and expressions).
{{ data.item }}—any. Inside the body: the current item. With "Items per iteration" above 1, an array of up to N items.{{ data.index }}—number. Inside the body: the rank of the iteration, starting at 0.{{ data.count }}—number. Inside the body: the number of iterations.{{ data.first }}—boolean. Inside the body:trueon the first iteration.{{ data.last }}—boolean. Inside the body:trueon the last iteration.{{ data.<step>.item }}—any. Inside the body: the same five values (item,index,count,first,last) under the loop's own name. In nested loops, the way to reach the outer loop's item.{{ data.<step>.status }}—string. Afterdone:done(all iterations succeeded),failed(some failed, with "Carry on and collect the error") ortimeout(time budget elapsed before the end).{{ data.<step>.count }}—number. Afterdone: the number of iterations planned (capped in a test run).{{ data.<step>.succeeded }}—number. Afterdone: iterations that succeeded.{{ data.<step>.failed }}—number. Afterdone: iterations that failed or were cancelled.{{ data.<step>.batchCount }}—number. Afterdone: iterations actually started.{{ data.<step>.items }}—array of { index, status, executionId, error, data }. Afterdone: one entry per iteration started.statusis the iteration's run status,errorits error code when it failed,datawhat the body produced, under the body nodes' names (e.g.{{ data.<step>.items.0.data.<body step>.category }}).{{ data.<step>.truncated }}—boolean. Afterdone:truewhen the collecteddataexceeded the instance budget; later entries keepindex,statusandexecutionIdonly. Absent otherwise.{{ data.<step>.simulatedLimit }}—number. Test runs only: the number of iterations actually run, when the list was longer.{{ data.<step>.total }}—number. Empty list only:0, withbatchSize,concurrencyandonItemErroras set.{{ data.<step>.summary }}—string. A one-line summary in the member's language, e.g. '12 iterations done, 1 failed.' (withsummaryKeyandsummaryParams).
Example
Log every attachment of an incoming email as a row of a table. Add a Loop node:
source: expression
items: {{ email.attachments }}
concurrency: 1
onItemError: continueOn item, connect Table — insert a row with:
values: [{ "column": "file", "value": "{{ data.item.filename }}" },
{ "column": "position", "value": "{{ data.index }}" },
{ "column": "sender", "value": "{{ email.from.email }}" }]On done, add a Condition on data.<step>.failed "is greater than" 0 to notify someone when a row could not be written. For an email with three attachments, the body runs three times, and after done {{ data.<step>.succeeded }} is 3.
Tips
- The body acts N times. A Send node inside the body sends one email per element. That is intended; check the list before publishing.
- The list must be a list. In expression mode, a value that is not an array (an empty string from a misspelt path, an object) makes the step fail with
node_invalid_paramrather than running a single iteration. An empty array is not an error: the loop takesdoneat once withcount0. - Limits. "Maximum number of iterations" (100 by default, at most 500) makes the loop fail with
loop_too_many_itemswhen the list is longer: it never processes part of a list silently. The administrator can lower the ceiling and the parallelism for the instance (LOOP_MAX_ITERATIONS,LOOP_MAX_CONCURRENCY, see environment variables). At most 5 iterations run in parallel, and at most 100 items per batch. - Order. With "Iterations in parallel" at 1 (the default) iterations run one after the other in list order. Above 1, the order is no longer guaranteed.
- Failures. With "Stop the loop" (default), no new iteration starts after a failure, those already running finish, and the loop fails with
loop_iteration_failed; the node's error policy then applies. With "Carry on and collect the error", the loop goes to the end and takesdonewithstatusfailedand the failures listed initems. - Time budget. When "Time budget (minutes)" elapses (60 by default, 24 hours at most), the loop takes
donewithstatustimeout. Iterations already started still run to completion. - Test runs. Only the first few iterations run (3 by default,
LOOP_SIMULATED_MAX_ITERATIONS), andsimulatedLimitsays so. Effects in the body stay simulated. - Wait and Approval work inside the body: the iteration's run is suspended, and the loop waits for it.
- Publishing checks the shape. The body cannot be empty, a node cannot be both in the body and after
done, a body node cannot receive a connection from outside the body (it already sees earlier data through the scope), and loops can be nested two levels deep at most.