No-code, low-code, and code
No-code platforms let you build entirely with visual blocks. Low-code platforms add places to write small expressions or scripts when needed. The building blocks map directly to what you learned earlier: a trigger starts the workflow (perception), AI nodes reason, branch nodes route, app nodes act, and approval steps provide human oversight.
Choose no-code when
- You want a working prototype in an afternoon
- The process connects common apps (email, sheets, chat)
- Non-programmers must maintain it
Choose code when
- Logic is complex or needs custom algorithms
- You need thorough automated tests and version control
- Performance, cost, or deployment constraints are strict
Common approach
- Prototype visually, then move critical parts to code
- Or call your Python code from a workflow node
Platforms at a glance
| Platform | Best known for | Hosting | Link |
|---|---|---|---|
| n8n | Workflow automation with many app integrations and built-in AI Agent nodes | Cloud or self-hosted (fair-code license) | n8n.io |
| Langflow | Visual builder for agents and RAG flows, built in Python | Open source, self-hosted or cloud | langflow.org |
| Flowise | Drag-and-drop LLM apps and agents | Open source, self-hosted or cloud | flowiseai.com |
| Dify | LLM application platform with workflows, RAG, and agents | Open source, self-hosted or cloud | dify.ai |
| Make | Visual automation scenarios across thousands of apps | Cloud | make.com |
| Zapier | App-to-app automation for business users, with AI features | Cloud | zapier.com |
| Microsoft Copilot Studio | Building agents inside the Microsoft 365 ecosystem | Cloud | Copilot Studio |
Features, pricing, and licenses change often. Check each platform's site before choosing.
Example 1 (n8n): email triage with human approval
Objective
Sort incoming email automatically and draft replies to questions, but never send anything without a person's approval.
Inputs
- New emails (Gmail trigger)
- A chat model credential
- A reviewer who approves drafts
Outputs
- Spam archived, urgent mail escalated
- Approved replies sent
- A log row for every email
Step by step
- Gmail Trigger checks for new email every minute.
- Text Classifier (an AI node) labels each email as spam, urgent, or question. It uses a chat model attached as a sub-node.
- Each label has its own output branch: spam is archived, urgent mail notifies on-call staff.
- For questions, an AI Agent node drafts a reply.
- A human approval step (for example, n8n's "send and wait for response" option on messaging nodes) pauses until a person approves.
- The reply is sent and the result is logged, for example to Google Sheets.
Import it in n8n by pasting the JSON onto the canvas or using Import from File. It is a starter: nodes ending in "(configure)" are placeholders for your own Gmail, approval, and logging nodes, and you must add your own credentials. Its structure was checked, but it has not been run against a live n8n instance, so node versions may need updating in newer releases.
The same logic in Python, with each function mapped to a node. Seeing both helps you decide when to stay visual and when to switch to code.
"""Python equivalent of the n8n "email triage with approval" workflow
Each function below matches one node in the visual workflow:
Trigger -> Classify (AI) -> Route (Switch) -> Draft reply (AI)
-> Human approval -> Send / Log
Seeing the same logic as code helps you understand what the visual nodes do,
and shows when moving from no-code to code makes sense.
Illustrative: classify() and draft_reply() stand in for LLM nodes.
"""
from datetime import datetime
NEW_EMAILS = [
{"from": "student@university.edu", "subject": "Can I join the crash course late?", "body": "I missed week 1."},
{"from": "promo@shop.com", "subject": "50% off laptops", "body": "Today only!"},
{"from": "dean@university.edu", "subject": "Urgent: room change", "body": "Workshop moves to Building 22."},
]
def trigger(): # n8n: Gmail / IMAP trigger node
return NEW_EMAILS
def classify(email): # n8n: AI Agent or Text Classifier node
text = (email["subject"] + " " + email["body"]).lower()
if "urgent" in text:
return "urgent"
if any(w in text for w in ("sale", "% off", "promo")):
return "spam"
return "question"
def draft_reply(email): # n8n: AI Agent node with a reply prompt
return f"Hello, thanks for your message about '{email['subject']}'. We will get back to you shortly."
def human_approval(draft): # n8n: "Send and wait for response" approval step
return "approve" # simulated: a person clicked Approve
def send(email, text): # n8n: Gmail "send" node
return f"sent to {email['from']}"
LOG = []
def log(email, category, outcome): # n8n: Google Sheets / database node
LOG.append({"time": datetime(2026, 1, 1, 9, 0).isoformat(), "from": email["from"], "category": category, "outcome": outcome})
for email in trigger():
category = classify(email)
if category == "spam": # n8n: Switch node, branch 1
log(email, category, "archived")
elif category == "urgent": # branch 2
log(email, category, "forwarded to on-call staff")
else: # branch 3
draft = draft_reply(email)
decision = human_approval(draft)
log(email, category, send(email, draft) if decision == "approve" else "rejected by reviewer")
for row in LOG:
print(row)
Example 2 (n8n): weekly research digest
Objective
Every week, fetch the newest papers on agentic AI and send a short, student-friendly summary.
Inputs
- A schedule
- The public arXiv API (no key needed)
- A chat model credential
Outputs
- A digest message with titles, one-line summaries, and links
Step by step
- A Schedule Trigger runs every Sunday at 9 am.
- An HTTP Request node calls the arXiv API for the five newest matching papers.
- An XML node converts the Atom feed into JSON.
- An LLM chain summarizes the papers using only the fetched data, which reduces invented details.
- A messaging node sends the digest.
Example 3 (Langflow, Flowise, or Dify): document Q&A
Objective
Answer students' questions about the course using only the syllabus and FAQ, with sources.
Inputs
- Course documents
- An embedding model and a chat model
- A user question
Outputs
- An answer grounded in the documents, with the source file
Step by step
- Load the documents and split them into chunks.
- Embed each chunk and store the vectors in a vector store (done once).
- When a question arrives, embed it and retrieve the most similar chunks.
- Insert the chunks into a prompt that tells the model to answer only from them, and show the sources.
These platforms ship ready-made RAG templates, so this flow takes minutes to assemble. Start from the template gallery on Langflow, Flowise, or Dify. Exports from those tools depend on their versions, so no export file is provided here. The Python equivalent shows exactly what each block does:
"""Python equivalent of a visual "document Q&A" (RAG) flow
Visual builders such as Langflow, Flowise, and Dify ship templates for
retrieval-augmented generation (RAG). The flow is the same in each tool:
Load documents -> Split into chunks -> Embed -> Store in a vector index
Question -> Embed -> Retrieve top chunks -> Prompt + LLM -> Answer with sources
Illustrative: we "embed" text as word-count vectors and use cosine similarity
so this runs without downloads. Real flows use an embedding model and a
vector database.
"""
import math
import re
from collections import Counter
DOCS = {
"syllabus.txt": "The crash course has ten modules. Module 5 covers the Model Context Protocol (MCP). "
"Module 10 covers governance, guardrails and human oversight.",
"faq.txt": "Notebooks open in Google Colab. You need basic Python. No paid API key is required "
"because examples use a stand-in model.",
}
def split(text): # "Text splitter" node (one chunk per sentence)
return [s for s in re.split(r"(?<=[.!?])\s+", text) if s]
def embed(text): # "Embeddings" node
return Counter(re.findall(r"[a-z0-9]+", text.lower()))
def cosine(a, b):
dot = sum(a[k] * b[k] for k in a)
return dot / ((math.sqrt(sum(v * v for v in a.values())) * math.sqrt(sum(v * v for v in b.values()))) or 1)
INDEX = [(name, chunk, embed(chunk)) for name, text in DOCS.items() for chunk in split(text)] # "Vector store" node
def retrieve(question, k=2): # "Retriever" node
q = embed(question)
return sorted(INDEX, key=lambda item: cosine(q, item[2]), reverse=True)[:k]
def answer(question): # "Prompt" + "LLM" nodes (stand-in)
hits = retrieve(question)
context = "\n".join(f"[{name}] {chunk}" for name, chunk, _ in hits)
prompt = f"Answer using only this context:\n{context}\n\nQuestion: {question}"
best_name, best_chunk, _ = hits[0]
return prompt, f"{best_chunk} (source: {best_name})"
for q in ["Which module covers MCP?", "Do I need an API key?"]:
prompt, reply = answer(q)
print("PROMPT SENT TO THE LLM:\n" + prompt)
print("ANSWER:", reply, "\n" + "-" * 60)
Tips for reliable visual workflows
- Keep credentials in the platform's credential store, never inside prompts or node text.
- Add an approval step before any node that sends, pays, or deletes.
- Use structured output (a fixed list of labels or a JSON schema) for AI nodes that feed branches.
- Log every run and review failed executions regularly.
- Export workflows as JSON and keep them in version control.