AI EngineeringZero to ProductionHome·About·Contact
No-Code Agentic AI · Chapter N3

Building with Make

Make (formerly Integromat) is the visual middle ground: a colorful scenario canvas with real logic — routers, iterators, aggregators, error handlers — that Zapier can't match, while staying more approachable than n8n. This chapter builds an AI scenario and teaches the data-flow model that makes Make click.

⏱️ ~1.5 hours🧪 1 labs🎯 Beginner→Tech-lead

Learning objectives

  • Build an AI scenario on Make's visual canvas.
  • Master the data-flow model: bundles, routers, iterators, aggregators.
  • Add error handling and control flow.
  • Choose Make vs Zapier vs n8n vs code.
▶ Runnable companionEvery code block in this lesson is also a standalone file under code/nc3-make/ in the course, with a README on how to run it. Read here, run there — no need to copy-paste.

1 · The visual middle ground essential

Make (formerly Integromat) is the visual middle ground: a scenario canvas with real logic — routers, iterators, aggregators, error handlers — that Zapier can't match, while staying more approachable than n8n. You wire modules on a canvas and watch data flow between them.

2 · The data-flow model essential

Data moves as bundles (records) through modules left-to-right. This mental model is the key to Make: a module runs once per bundle it receives, so how many bundles flow controls how many times downstream modules run.

PrimitiveDoesAnalogy
Routersplit into branchesif/elif
Iteratorone bundle → manyfor-loop / flatten
Aggregatormany bundles → onereduce / join
Error handlercatch + route failurestry/except

3 · Intermediate — iterators & aggregators intermediate

The pattern that trips people up: an iterator explodes an array into N bundles (so each runs downstream), then an aggregator collapses results back into one. Get this and Make clicks; miss it and you get N duplicate emails.

Example code for learning — review, test, and adapt it before running against real or production systems. Commands can create, change, or delete resources. See the Terms & Disclaimer.
Python · iterator → process → aggregator (runs)
scenario.pydef scenario(order):
    # iterator: one order with many line items -> a bundle per item
    items = order["items"]
    # each bundle: "AI" categorizes the item
    processed = [{"name": it["name"],
                  "category": "food" if it["perishable"] else "goods"}
                 for it in items]
    # aggregator: collapse bundles back to one summary
    summary = {}
    for pi in processed:
        summary[pi["category"]] = summary.get(pi["category"], 0) + 1
    return summary

print(scenario({"items": [{"name":"milk","perishable":True},
                          {"name":"book","perishable":False},
                          {"name":"eggs","perishable":True}]}))
{'food': 2, 'goods': 1}
▶ How this works

In Make you don't write code — you drag modules onto a canvas and data flows through them left-to-right as bundles (think: one bundle = one record). A module runs once per bundle it receives. This Python function is a plain stand-in for a real Make scenario so you can see that flow on one screen: it takes an order that contains a list of line items, categorizes each item, then counts up the categories. The three comments (# iterator, # each bundle, # aggregator) mark the exact points where a Make scenario would use each of its three key primitives.

  1. Iterator (one bundle → many). items = order["items"] pulls the array of line items out of the single incoming order. In Make, an Iterator module takes that one array and explodes it into one bundle per item, so everything after it runs once per item — like a for-loop that Make draws for you.
  2. Process each bundle (the 'AI' step). The list comprehension (processed = [ ... for it in items]) is the work done to every item: here it labels each item "food" if it is perishable, otherwise "goods". In a real scenario this middle module might be an AI call that categorizes the item — and it fires once per bundle the iterator produced.
  3. Aggregator (many bundles → one). The for pi in processed loop tallies how many items fell into each category into a single dictionary summary. This is Make's Aggregator module: it collapses the many per-item bundles back down to one combined result — otherwise you'd fire the next step (an email, say) once per item instead of once for the whole order.
  4. The final print(scenario({...})) feeds in a sample order with three items (milk, book, eggs) so you can run the file and watch one order fan out to three bundles and collapse back to one summary.

What the output means: {'food': 2, 'goods': 1} — two perishable items (milk, eggs) became food and one (book) became goods. One order went in, three bundles flowed through the middle, and the aggregator returned a single count. That 'many bundles collapse to one' is the whole point of an aggregator.

Try this: Add a fourth item such as {"name":"cheese","perishable":True} to the list and re-run — the food count jumps to 3. That is exactly the Make surprise to watch for: more items in the array means more bundles, which means the middle module (and its cost) runs more times.

4 · Advanced — error handling & control advanced

Make's error handlers attach to a module and route failures (Resume, Rollback, Ignore, Break) — real reliability logic Zapier lacks. Combined with routers and filters, you build scenarios that degrade gracefully instead of dying on one bad bundle.

Bundles explain 'why did this run 40 times?'Almost every Make surprise is a bundle-count surprise: an iterator or an array output multiplied your bundles. When a scenario misbehaves, count the bundles flowing into the module — that's the debugging reflex.

5 · Professional — operations budget professional

Make bills by operations (module executions). Iterators multiply operations fast, so design for efficiency: filter early, aggregate before expensive modules, and avoid running AI steps per bundle when one batched call would do. Monitor the operations log.

6 · Tech-lead — placing Make in the toolbox tech-lead

A lead maps the no-code/low-code ladder: Zapier for simple linear automations, Make for visual logic with branching/looping, n8n for self-hosted/code-heavy control, and real code when it needs tests, version control, and scale. Make is the sweet spot for complex-but-not-code automation — know when a scenario has outgrown it.

The automation ladderZapier → Make → n8n → code is a complexity ladder. Each rung buys more control at the cost of more effort. A lead picks the lowest rung that meets the need and plans the jump to the next when a workflow outgrows it.

Exercise NC3.1 — Build an iterator/aggregator scenario

Context: The exercise that ties the module together is the fan-out/fan-in pattern with an error path bolted on — and being able to say what each of Make's control primitives maps to in code.

Your task: Design a Make scenario that iterates an array, runs an AI categorization per item, and aggregates the result; model it with scenario.py, add an error path, and map Make's four control primitives to code.

Requirements:

  • An Iterator fans the array out; an AI module categorizes each bundle; an Aggregator merges results back to one bundle
  • An error path (retry / Break to dead-letter) covers a failing per-item call
  • scenario.py reproduces the iterate→process→aggregate flow offline
  • Each control primitive (Iterator, Aggregator, Router, error handler) is mapped to its code equivalent (for-each, reduce, if/branch, try/except)

💡 Hint: Model the fan-out as a loop and the aggregator as a reduce — the error handler is just the try/except wrapped around the per-item call.

🪜 Practice ladder beginner → industry

Six graded exercises, easy to real-world. Try each before opening its solution.

Exercise 1 · Modules and the bundle data-flowBeginner

Context: Make connects modules that pass “bundles” — its unit of data, much like an n8n item or a dict. A scenario is just a chain of modules moving bundles forward.

Your task: Design a scenario for “watch new files in Drive → OCR → store text in Airtable”, naming each module and what its bundle carries.

Requirements:

  • A trigger module (Watch Files) emits one bundle per new file
  • Each downstream module adds fields to the bundle (OCR adds text)
  • The final module writes the bundle's text to an Airtable record
  • Fields are referenced by mapping from earlier modules, e.g. {{1.fileId}}, {{2.text}}

💡 Hint: A bundle is Make's dict-equivalent — trace which module first sets each field the later modules read.

Show solution

Make's linear module chain (a 'scenario'):

[ Google Drive: Watch Files ] --bundle--> [ OCR module ] --bundle--> [ Airtable: Create Record ]
   emits 1 bundle per new file        adds bundle.text            writes text to a row

A bundle is Make's unit of data (like an n8n item / a dict). Fields are referenced by mapping from earlier modules, e.g. {{1.fileId}}, {{2.text}}.

Exercise 2 · The data-flow model: mapping expressionsIntermediate

Context: Make lets you transform data inside a mapping with built-in functions, so you often need fewer modules than a strictly node-per-step tool. The inline conditional is the sharpest of these.

Your task: Design an Airtable record whose field mappings call Make functions (upper, formatDate, and the inline if) to derive values from earlier modules.

Requirements:

  • At least one field is transformed inline (e.g. {{ upper(1.name) }})
  • A date field is formatted with formatDate(now; "YYYY-MM-DD")
  • A status field uses if(cond; then; else) — note the semicolon-separated arguments
  • The inline logic replaces what would otherwise be extra modules

💡 Hint: Make's if() uses semicolons, not commas, to separate its arguments — the inline transform is why you need fewer modules than a node-per-step tool.

Show solution

Mappings can call built-in functions inline:

Airtable "Create Record" field mappings:
  Name    = {{ upper(1.name) }}
  Created = {{ formatDate(now; "YYYY-MM-DD") }}
  Status  = {{ if(2.confidence > 0.8; "auto"; "review") }}
  Source  = {{ 1.fileName }}

The if(cond; then; else) function is Make's inline conditional (note the semicolon separators). Equivalent logic offline:

def status(confidence):
    return "auto" if confidence > 0.8 else "review"
print(status(0.9), status(0.5))   # auto review

Make's expressions let you transform data inside a mapping, so you often need fewer modules than in a purely node-per-step tool.

Exercise 3 · Iterators and aggregatorsAdvanced

Context: Make's signature feature is the split/merge pair: an Iterator fans one array-bearing bundle out into many bundles, and an Aggregator fans them back into one. This is the array/loop work a linear Zap cannot do.

Your task: Design “split an order's line items → price each → sum back to one total” using an Iterator and an Aggregator.

Requirements:

  • The order arrives as one bundle with an array field of line items
  • An Iterator emits N bundles, one per line item
  • A module prices each bundle, adding a per-item line total
  • A numeric Aggregator (SUM) merges the N bundles back into one bundle carrying the total

💡 Hint: Iterator = for-each fan-out, Aggregator = reduce back to one — the loop lives between the split and the merge, not in a single module.

Show solution

The split/merge pattern that defines Make:

[ Webhook: order ]
      |  order.items = [ {..}, {..}, {..} ]   (one bundle, an array field)
[ Iterator: order.items ]      --> emits N bundles, one per line item
[ Set/HTTP: price each item ]  --> each bundle now has .lineTotal
[ Array Aggregator (Numeric: SUM of lineTotal) ] --> back to ONE bundle: {total}
[ Airtable: write total ]

The fan-out / fan-in, modeled offline:

def iterate_price_aggregate(items, price):
    per = [{"sku": i["sku"], "lineTotal": i["qty"] * price[i["sku"]]} for i in items]
    total = sum(p["lineTotal"] for p in per)          # the Aggregator step
    return per, total

items = [{"sku":"A","qty":2}, {"sku":"B","qty":1}]
per, total = iterate_price_aggregate(items, {"A":10, "B":25})
print(per); print("total =", total)   # total = 45

Iterator = for-each fan-out; Aggregator = reduce back to one bundle. This pair is why Make handles array/loop work that a linear Zap cannot.

Exercise 4 · Error handling & control (routers, breaks)Expert

Context: Make attaches error handlers to individual modules (Resume / Rollback / Break) and branches with Routers. Combined, they let a flaky API call retry, then park a failed bundle in a dead-letter store instead of losing the event.

Your task: Design robust handling for a flaky API call: retry with backoff, a Break to a dead-letter store on final failure, and a Router that branches on the response.

Requirements:

  • The risky module carries a Break error handler with a retry count and interval
  • On final failure the bundle is parked in a data-store dead-letter (bundle + error), not dropped
  • A Router branches after the call — a success route to processing, a fallback route to an alert
  • The retry uses increasing waits (backoff), and no event is silently lost

💡 Hint: Break parks the failing bundle so the scenario keeps running on the rest — the dead-letter store is what guarantees the parked event is never silently lost.

Show solution

Attach an error handler directive to the risky module:

[ HTTP: call API ]
   error handler:  Break (retries: 3, interval: 60s)  -- retry, then park the bundle
      on final failure --> [ Data store: dead-letter (bundle + error) ]

Router (branch after success):
   Route 1  filter: {{status = 200}}   --> [ process ]
   Route 2  fallback                   --> [ Slack: alert ]

Retry-with-backoff, modeled offline:

def call_with_retry(call, tries=3, base_s=60):
    for attempt in range(1, tries + 1):
        ok = call(attempt)
        if ok:
            return f"success on attempt {attempt}"
        wait = base_s * (2 ** (attempt - 1))   # exponential backoff
        # (Make waits `interval`; shown here as the schedule)
    return "dead-letter after retries"

print(call_with_retry(lambda a: a == 2))   # success on attempt 2
print(call_with_retry(lambda a: False))     # dead-letter after retries

Break parks a failing bundle so the scenario keeps running; the dead-letter store means no event is silently lost — the reliability spine of a Make scenario.

Exercise 5 · The operations budget (operations = module runs)Professional

Context: Make bills per operation — roughly one module execution per bundle — and an Iterator multiplies operations because every module after it runs once per fanned-out bundle. Sizing a scenario means sizing the fan-out, not the run count.

Your task: Build a calculator that estimates monthly operations for a fan-out scenario given runs per day, modules before the iterator, items per run, and modules after the iterator.

Requirements:

  • Modules before the Iterator run once per run (one bundle)
  • Modules after the Iterator run once per fanned-out item, so they multiply by items-per-run
  • Monthly total scales the per-run cost by runs/day and days
  • The estimate makes the Iterator's multiplier the dominant term

💡 Hint: The cost trap is the module count after the Iterator — that block is multiplied by the fan-out, so it dominates the operation count.

Show solution

Operations scale with bundles, and Iterators fan out. Runnable estimator:

def monthly_ops(runs_per_day, modules_before_iter, items_per_run,
                modules_after_iter, days=30):
    # before the iterator: 1 bundle; after: items_per_run bundles
    per_run = modules_before_iter + items_per_run * modules_after_iter
    return per_run * runs_per_day * days

ops = monthly_ops(runs_per_day=500, modules_before_iter=2,
                  items_per_run=10, modules_after_iter=3)
print(f"~{ops:,} operations/month")   # iterator multiplies the after-modules by 10

The lesson's cost trap: an Iterator turns one run into N bundles, so every module after it multiplies your operation count — size scenarios by the fan-out, not the run count.

Exercise 6 · Place Make in the no-code toolboxIndustry scenario

Context: Zapier, Make, and n8n form a complexity ladder: each rung buys more control for more effort. A lead chooses per automation — not once for the org — based on loops, connector breadth, self-hosting, and cost model.

Your task: Encode the Make-vs-Zapier-vs-n8n selection criteria into a router that recommends a tool given whether loops are needed, self-hosting is required, connector breadth is critical, and volume is high.

Requirements:

  • A self-hosting requirement points to n8n (per-run pricing, code nodes)
  • A need for loops/array work points to Make (iterators/aggregators)
  • A need for the widest connector catalog on simple linear flows points to Zapier
  • The router returns a concrete tool with its reason, chosen per automation

💡 Hint: There is no single org-wide answer — route each automation by its dominant need (loops, self-host, or connector breadth).

Show solution

Decision router across the three tools from the module:

def pick_tool(needs_loops, self_host_required, connector_breadth_critical,
             high_volume):
    if self_host_required:
        return "n8n (self-hostable, per-run not per-task, code nodes)"
    if needs_loops or connector_breadth_critical is False:
        return "Make (iterators/aggregators, visual data-flow)"
    if connector_breadth_critical and not needs_loops:
        return "Zapier (widest connector catalog, simplest linear Zaps)"
    return "prototype in whichever the team knows"

print(pick_tool(True, False, False, False))    # Make (loops)
print(pick_tool(False, True, False, True))      # n8n (self-host + volume)
print(pick_tool(False, False, True, False))     # Zapier (connectors)

The portfolio view: Zapier for connector breadth and simple flows, Make for array/loop-heavy data work, n8n when you need self-hosting or per-run economics — pick per automation, not once for the org.

✓ Checkpoint — you can move on when you can…

  • Build a visual AI scenario.
  • Explain bundles, routers, iterators, aggregators.
  • Add error handling; manage the operations budget.
  • Place Make vs Zapier/n8n/code on the ladder.

Knowledge check check yourself

✓ Knowledge check

In Make, what does an iterator do to a bundle, what does an aggregator do, and why does forgetting the aggregator cause the classic 'why did this run 40 times?' bug?

Show answer
An iterator explodes one bundle (e.g. an array) into N bundles so downstream modules run once per item; an aggregator collapses the many bundles back into one. Without the aggregator, the next module (e.g. an email) fires once per item instead of once for the whole record.
✓ Knowledge check

Make bills by operations (module executions). Given how bundles multiply, what design guidance does the lesson give to control the operations budget?

Show answer
Filter early, aggregate before expensive modules, and avoid running AI steps per bundle when one batched call would do — because iterators multiply operations fast.
© 2026 studybydoing.in · AI Engineering: Zero to Production · All rights reserved. · About · Privacy Policy · Terms · Contact
Educational content, provided as-is and without warranty. Code samples are examples — review, test, and adapt them before using in production. See the Terms of Use & Disclaimer. Use at your own risk.
© studybydoing.in