I like n8n. It is the fastest way I know to get an AI idea working and in front of a client. My WhatsApp shopping bot, JITBot, started there, and for a while it was great.

Then the workflow grew to roughly 150 nodes across 10 workflows, and every small change started to feel risky. In the end I rebuilt the whole thing in Python with FastAPI. This post is about why, how I decided, and what I learned. It is not an "n8n is bad" post. It is about knowing when a tool stops fitting the job.

What JITBot does

JITBot is a WhatsApp assistant for shopping. A customer sends a message, the bot understands what they want, looks up products, helps them build an order and hands difficult cases to a human.

That sounds simple, but a real conversation has memory. The bot must remember what is in the cart, what it just asked and whether the customer has already confirmed something. That "memory" is where most of my problems came from.

The signs that n8n was getting in the way

Tool-calling that failed in strange ways

The AI agent was meant to call tools, such as searching products or creating an order. Sometimes it called the wrong tool, sometimes it skipped one, and sometimes a call failed without a clear error. In a visual workflow it was hard to see exactly what the model had received at each step.

Session state in Redis that went stale

I kept each customer's conversation state in Redis. The trouble was old state leaking into new conversations. A customer who had been escalated to a human yesterday could come back today and still be treated as "escalated". The bot would go quiet or give odd replies.

The confirmation loop

The bot would ask "Do you want to confirm this order?", the customer would say yes, and the bot would ask again. The cause was that the "confirmed" flag was not saved in the right place before the next message arrived. It took a long time to find because the problem was spread over several nodes.

The audit that made the decision for me

Before deciding anything, I audited the whole n8n setup. It turned up 24 separate issues, including:

  • A broken order flow in one branch
  • References to AI models that no longer existed or were named wrongly
  • Database queries against MongoDB that returned the wrong data

None of these were exotic. They were the kind of problems that build up quietly in any large workflow when many small edits are made over time. But 24 issues in one system told me that the structure itself had become hard to keep correct.

Why FastAPI

I rebuilt the whole system in Python with FastAPI, replacing around 150 nodes. The reasons were practical:

  • Real version control. Every change is a visible code diff that I can review and roll back.
  • Tests. I can test the order flow and intent detection with fixed examples before deploying.
  • Clear state handling. Session state lives in one module instead of being scattered across nodes.
  • Easier debugging. I can log exactly what the model received and returned.

For session state I now save a small object per customer with an expiry, so old state cannot live forever:

import json
import redis

r = redis.Redis(host="localhost", port=6379, decode_responses=True)

SESSION_TTL_SECONDS = 60 * 30  # choose what fits your business

def save_state(user_id: str, state: dict) -> None:
    r.setex(f"session:{user_id}", SESSION_TTL_SECONDS, json.dumps(state))

def load_state(user_id: str) -> dict:
    raw = r.get(f"session:{user_id}")
    return json.loads(raw) if raw else {"stage": "new"}

def clear_state(user_id: str) -> None:
    r.delete(f"session:{user_id}")

The expiry fixes the stale escalation problem on its own, and I also call clear_state the moment a human conversation is closed.

The prompt needed fixing too

Moving to code did not fix everything. The system prompt was still causing trouble, so I kept refining it. By version 6.1 I had focused on two things:

  • Stale escalation state: the prompt must not assume a customer is still escalated just because of an old message.
  • Intent detection: I made the rules for deciding "is this a question, an order, or a complaint?" more precise, so the bot stopped guessing.

The lesson here is that a prompt is part of your code. It needs versions, notes about what changed, and test messages, just like any other file.

When I would still use n8n

I still use n8n often. It is excellent when:

  • You are building a first version and speed matters most
  • The flow is mostly linear: trigger, a few steps, send a result
  • The client wants to see and understand the workflow visually
  • It is an internal automation, such as sending a report or moving data between tools

I move to code when the logic has many branches, lots of conversation state, or needs proper tests.

What I would do differently

  • Set a "graduation" point early. If a workflow passes a certain size or has complex state, plan the move before it becomes painful.
  • Audit regularly. A short review every few weeks would have caught those 24 issues one at a time.
  • Save conversation state in one place from the very beginning.
  • Write test conversations for the order flow before changing anything.

Final thoughts

The right tool depends on the stage of the project. n8n got JITBot to a working product quickly. FastAPI made it something I could trust and keep improving. Knowing when to switch is a skill of its own.

If you are deciding between n8n and custom code for a WhatsApp or AI project, feel free to contact me through the contact page. I am happy to talk through your case.

✳ ✳ ✳