
n8n + Python + the OpenAI API: automate the boring half of your job
Article Summary
For the professional drowning in repetitive work, not the developer. A worked example: form submissions classified by an LLM and routed to the right place, automatically.
A student of mine — a project manager, not a programmer — told me she spent the first forty minutes of every morning doing the same thing. Open the shared inbox. Read each new enquiry. Decide whether it was a sales lead, a support question, or spam. Copy the sales ones into a spreadsheet, forward the support ones to a colleague, and bin the rest. Forty minutes a day, five days a week, every week, forever. She wasn't asking me to teach her to code. She was asking whether a computer could just do that.
The answer is yes, and the honest version of that answer is what this post is about. Not "AI will automate everything" — I have no patience for that. Just: there is a specific, learnable way to hand a computer a boring, rule-ish, repetitive task and have it run unattended. The tool I reach for is n8n, and the interesting part is where a small amount of Python and one OpenAI call slot into it.
What n8n actually is
n8n (you say it "n-eight-n", short for "nodern", though everyone just spells it out) is a workflow automation tool. You build a workflow by dragging boxes called nodes onto a canvas and connecting them with lines. Each node does one thing — watch for a new email, call an API, write a row to Google Sheets — and the line between two nodes means "when this finishes, pass the data to the next one."
If you've heard of Zapier or Make, it's the same category. The reasons I point students at n8n specifically:
- You can self-host it. n8n is fair-code licensed, with a free Community Edition you can run on your own machine or a cheap server. Your data doesn't have to leave your control. There's also a paid cloud option if you'd rather not run a server.
- It has a real Code node. When the drag-and-drop boxes can't express your logic, you drop in a node and write actual JavaScript or Python. You're never stuck.
- It's visual but not a toy. You can see the data flowing through, inspect what each node received and produced, and debug by clicking around rather than reading logs.
The current major version is n8n 2.0, which reached stable release in December 2025. One change in that release matters for us: Code-node executions now run in isolated environments (task runners) by default, which is the plumbing that makes the Python story work.
Triggers vs. actions: the only mental model you need
Every workflow starts with exactly one trigger node and then has any number of action nodes. That's the whole mental model, and it's worth internalising before anything else:
| Trigger node | Action node | |
|---|---|---|
| Role | Starts the workflow | Does work once started |
| How many | Exactly one, at the top | As many as you need |
| Examples | "New form submission", "Every day at 9am", "New email" | "Call OpenAI", "Add row to Sheet", "Send Slack message" |
| Mental model | The when | The what |
A trigger is the when ("when a form is submitted…") and the actions are the what ("…classify it, then route it"). If you can describe your boring task as one "when" and a few "thens", you can probably build it in n8n.
The worked example: inbox → classify → route
Let's build my student's morning. The plan in plain English: when a new enquiry arrives, ask an LLM to label it as sales, support, or spam, then send it to the right place. Here's the shape of the workflow:
[Trigger: new form submission]
|
v
[OpenAI: classify the message]
|
v
[Code node (Python): clean up the label]
|
v
[Switch: route by label] ---> sales: [Google Sheets: add row]
---> support: [Slack: send message]
---> spam: [No-op: do nothing]Five conceptual steps. Let me walk each one.
Step 1 — the trigger
Drop a trigger node on the canvas. For an enquiry form, the Form Trigger gives you a hosted form with its own URL; every submission fires the workflow and the field values arrive as data. If your enquiries come by email instead, swap in an email trigger — the rest of the workflow doesn't change, which is exactly the point of the node model. The trigger's job is done the moment it hands the next node a little packet of data, in n8n's case a JSON object with the form fields.
Step 2 — the OpenAI classification
Add an OpenAI node (or a generic HTTP Request node pointed at the API — either works) and give it a tight instruction. The prompt is the entire intelligence of this workflow, so it's worth getting right. Something like:
You are a triage assistant. Read the enquiry below and respond with
exactly one word: "sales", "support", or "spam". No punctuation, no
explanation.
Enquiry: {{ $json.message }}That {{ $json.message }} is n8n's expression syntax — it pulls the message field out of the data the previous node passed in. The model reads the enquiry and returns a single word.
You may have read my other post arguing that for anything feeding code you want structured outputs — a hard schema guarantee — rather than trusting a free-text reply. That's still true, and for a production-grade build you'd absolutely have the model return a strict JSON object with an enum. For a first n8n workflow, a one-word answer plus the cleanup in the next step is a perfectly honest place to start, and it keeps the moving parts visible while you're learning. Walk before you run.
Step 3 — the Python Code node (the one snippet)
LLMs are mostly obedient. Ask for one word and you'll usually get one word — but every so often you get "Sales." with a capital and a full stop, or " support" with a stray space, or "This looks like sales.". If you route on the raw value, those slip through the cracks. So we normalise it.
This is where Python earns its place. n8n added native Python support via task runners in version 1.111.0, and in n8n 2.0 it's the standard way the Code node runs Python. Add a Code node, set the language to Python, and set it to run once for each item:
# n8n native-Python Code node, "Run Once for Each Item" mode.
# The incoming item is exposed as `_item`. Native Python uses
# bracket access notation, e.g. _item["json"]["field"].
raw = _item["json"]["message"] # the OpenAI node's text output
label = raw.strip().lower().rstrip(".") # "Sales." -> "sales"
# Defensive default: anything we don't recognise becomes spam,
# so a weird model reply can never get mis-routed to a real human.
if label not in ("sales", "support", "spam"):
label = "spam"
_item["json"]["label"] = label
return _itemA few things worth knowing so this doesn't bite you. In n8n's native Python, the per-item object is _item and you reach into it with bracket notation — _item["json"]["message"], not the dotted _item.json.message you might expect. (In "run once for all items" mode you'd loop over _items instead.) And there's one real gotcha that surprises people: on n8n Cloud, the Python Code node can't import libraries at all — not even the standard library. If you need import requests or import pandas, you have to self-host n8n, where you can allowlist the modules you want via the task-runner image. The snippet above is deliberately pure Python with no imports, so it runs on either.
If you don't need a library, native Python is lovely for exactly this kind of small, surgical data-shaping that's clumsy to express in drag-and-drop boxes. If you want the full Python ecosystem, that's your nudge toward self-hosting — which is its own small project but very much within reach. This kind of glue work, where a little code makes an otherwise no-code tool dramatically more capable, is a sweet spot I spend a lot of time on in AI automation sessions.
Step 4 — route with a Switch
Add a Switch node. It reads {{ $json.label }} and sends the data down a different branch depending on the value: one output for sales, one for support, one for spam. Think of it as the workflow's fork in the road.
Step 5 — the actions on each branch
- sales → a Google Sheets node set to "append row", mapping the enquiry fields into columns. Her spreadsheet now fills itself.
- support → a Slack node that posts the enquiry into a channel, or an email node that forwards it to the colleague.
- spam → a No-op node, or simply nothing. The branch ends and the message quietly dies, which is all she ever did with it anyway.
Hit "Activate" and the workflow runs unattended every time a form comes in. Forty minutes a day, handed to a machine.
When NOT to automate
I'd be doing you a disservice if I only sold you the upside. Automation has a real cost — you have to build it, and then you have to maintain it when the form changes or the API updates or an edge case you didn't foresee shows up. That cost is only worth paying under specific conditions:
- The task is high-volume and stable. Automating something you do twice a year, or something whose rules change every month, will cost you more time than it saves. Forty-minutes-every-morning clears the bar easily.
- A wrong answer is cheap to recover from. Mis-filing a sales lead into the support channel is a shrug; a colleague forwards it back. If a mistake is expensive or irreversible — anything touching money, legal commitments, or "you can't un-send this" — keep a human in the loop, or have the automation draft and a person approve.
- You can describe the rules. If you can't articulate how you decide, an LLM will guess, and you've automated a coin flip. Automate the parts you understand; leave the genuinely judgement-heavy calls to yourself.
The honest test: would you trust a brand-new, slightly literal-minded intern to do this from a written instruction sheet? If yes, automate it. If the answer is "well, it depends, you have to use judgement" — that dependency is exactly the thing the machine can't yet supply.
Error handling, the bare minimum
Unattended means nobody is watching when it breaks — and it will break, usually because the OpenAI API had a blip or a form field arrived empty. Two habits keep a small failure from becoming a silent disaster:
- Set an error workflow. n8n lets you designate a workflow that runs whenever another one fails. A two-node error workflow that just messages you on Slack — "the triage workflow failed at 09:14" — means you find out from a notification, not from an angry customer three days later.
- Decide what a failed item should do. On a node's settings you can choose to continue on error rather than halt the whole run. For triage, you might let a failed classification fall through to the spam/manual branch so one bad item doesn't block the queue. Choose deliberately; don't leave it to chance.
That's genuinely the minimum, and it's enough to start. You can get fancier later.
An honest word on the learning curve
n8n is friendlier than writing the equivalent script from scratch, but it is not effortless, and I'd rather you hear that from me than discover it at hour three. Expressions like {{ $json.message }} take a little getting used to. Connecting to OpenAI and Google means wrangling API keys and credentials, which is fiddly the first time. And the Python Code node has the sharp edges I flagged — the _item bracket access, the no-imports-on-cloud rule — that aren't obvious until you hit them.
The good news is that it's learnable in an afternoon for one concrete workflow, and the second workflow takes a fraction of the time because the mental model transfers wholesale. Build the boring-inbox example end to end, even if it's slightly clumsy, and you'll understand the shape of every automation you build afterwards.
The recap
- n8n is a visual workflow tool: one trigger (the when) plus action nodes (the what), connected on a canvas. Self-hostable, fair-code, free Community Edition.
- The pattern that earns its keep: trigger → classify with one OpenAI call → normalise with a tiny Python Code node → route with a Switch → act.
- Native Python runs via task runners (since 1.111.0, standard in n8n 2.0). Use
_itemwith bracket access. Cloud can't import libraries; self-host if you need them. - Only automate high-volume, stable, recoverable, describable tasks. When judgement is the job, keep yourself in the loop.
- Set an error workflow so unattended doesn't mean unnoticed.
The whole game is finding the forty-minutes-every-morning task in your own week and handing it to a machine that doesn't get bored.
If you've got one of those tasks and you'd like a hand turning it into a working n8n flow — wiring the OpenAI step, getting the Python Code node to behave, deciding what's safe to automate and what isn't — that's exactly the kind of thing we can build together. The first session is free, and you're welcome to bring your real boring task to AI automation so we leave with something that actually runs. No pressure.
Enjoyed this post? Get the next one in your inbox.
A short, useful email when there's a new tutorial, study guide, or career-prep post on the blog. No spam, unsubscribe anytime.
Written by Ali Jabbary
M.Sc., P.Eng. • Expert Data Scientist & ML Engineer with 10+ years of experience. 500+ students helped worldwide. Specializing in Python, AI/ML, and turning complex problems into simple solutions.


