Skip to content

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: true on the first iteration.
  • {{ data.last }} — boolean. Inside the body: true on 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. After done: done (all iterations succeeded), failed (some failed, with "Carry on and collect the error") or timeout (time budget elapsed before the end).
  • {{ data.<step>.count }} — number. After done: the number of iterations planned (capped in a test run).
  • {{ data.<step>.succeeded }} — number. After done: iterations that succeeded.
  • {{ data.<step>.failed }} — number. After done: iterations that failed or were cancelled.
  • {{ data.<step>.batchCount }} — number. After done: iterations actually started.
  • {{ data.<step>.items }} — array of { index, status, executionId, error, data }. After done: one entry per iteration started. status is the iteration's run status, error its error code when it failed, data what the body produced, under the body nodes' names (e.g. {{ data.<step>.items.0.data.<body step>.category }}).
  • {{ data.<step>.truncated }} — boolean. After done: true when the collected data exceeded the instance budget; later entries keep index, status and executionId only. 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, with batchSize, concurrency and onItemError as set.
  • {{ data.<step>.summary }} — string. A one-line summary in the member's language, e.g. '12 iterations done, 1 failed.' (with summaryKey and summaryParams).

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: continue

On 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_param rather than running a single iteration. An empty array is not an error: the loop takes done at once with count 0.
  • Limits. "Maximum number of iterations" (100 by default, at most 500) makes the loop fail with loop_too_many_items when 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 takes done with status failed and the failures listed in items.
  • Time budget. When "Time budget (minutes)" elapses (60 by default, 24 hours at most), the loop takes done with status timeout. Iterations already started still run to completion.
  • Test runs. Only the first few iterations run (3 by default, LOOP_SIMULATED_MAX_ITERATIONS), and simulatedLimit says 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.