nested-loop-sees-outer-item.json 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. {
  2. "name": "nested-loop-sees-outer-item",
  3. "description": "A node inside a nested loop must still be able to reference the OUTER loop's item. Reproduces the Email OCR failure of 2026-09-04: build_email sits in a loop over one email's attachments, nested in a loop over emails, and {{email.from}} resolved to empty - so the reply was addressed to nobody and the send failed with \"At least one recipient is required\". The inner loop deliberately uses a different item variable name, and the assertion checks both are visible at once.",
  4. "nodes": [
  5. {
  6. "id": "n1",
  7. "name": "Trigger",
  8. "type": "click-trigger",
  9. "position": {
  10. "x": 0,
  11. "y": 0
  12. },
  13. "config": {}
  14. },
  15. {
  16. "id": "seed",
  17. "name": "Seed",
  18. "type": "code",
  19. "position": {
  20. "x": 0,
  21. "y": 100
  22. },
  23. "config": {
  24. "code": "return { mails: [{ from: 'a@example.com', parts: ['p1', 'p2'] }] };"
  25. }
  26. },
  27. {
  28. "id": "outer",
  29. "name": "Outer",
  30. "type": "loop",
  31. "position": {
  32. "x": 0,
  33. "y": 200
  34. },
  35. "config": {
  36. "inputField": "result.mails",
  37. "itemVariableName": "email",
  38. "continueOnError": false
  39. }
  40. },
  41. {
  42. "id": "inner",
  43. "name": "Inner",
  44. "type": "loop",
  45. "position": {
  46. "x": 240,
  47. "y": 200
  48. },
  49. "config": {
  50. "inputField": "data.parts",
  51. "itemVariableName": "part",
  52. "continueOnError": false
  53. }
  54. },
  55. {
  56. "id": "build",
  57. "name": "Build",
  58. "type": "set-fields",
  59. "position": {
  60. "x": 480,
  61. "y": 200
  62. },
  63. "config": {
  64. "mode": "only-set",
  65. "fields": [
  66. {
  67. "name": "outerFrom",
  68. "value": "{{email.from}}"
  69. },
  70. {
  71. "name": "innerPart",
  72. "value": "{{part}}"
  73. }
  74. ]
  75. }
  76. },
  77. {
  78. "id": "after",
  79. "name": "After",
  80. "type": "set-fields",
  81. "position": {
  82. "x": 240,
  83. "y": 340
  84. },
  85. "config": {
  86. "mode": "only-set",
  87. "fields": [
  88. {
  89. "name": "done",
  90. "value": "yes"
  91. }
  92. ]
  93. }
  94. }
  95. ],
  96. "connections": [
  97. {
  98. "sourceNodeId": "n1",
  99. "sourceOutput": "main",
  100. "targetNodeId": "seed",
  101. "targetInput": "data"
  102. },
  103. {
  104. "sourceNodeId": "seed",
  105. "sourceOutput": "main",
  106. "targetNodeId": "outer",
  107. "targetInput": "data"
  108. },
  109. {
  110. "sourceNodeId": "outer",
  111. "sourceOutput": "loop",
  112. "targetNodeId": "inner",
  113. "targetInput": "data"
  114. },
  115. {
  116. "sourceNodeId": "inner",
  117. "sourceOutput": "loop",
  118. "targetNodeId": "build",
  119. "targetInput": "data"
  120. },
  121. {
  122. "sourceNodeId": "outer",
  123. "sourceOutput": "done",
  124. "targetNodeId": "after",
  125. "targetInput": "data"
  126. }
  127. ],
  128. "expect": {
  129. "build": {
  130. "status": "completed",
  131. "output": {
  132. "outerFrom": "a@example.com",
  133. "innerPart": "p2"
  134. }
  135. }
  136. }
  137. }