I built a convesational brain for a client in N8N. They like to call it Mira. Mira is the WhatsApp sales line for a landscaping business in Dubai. Customers write in English, Arabic, Urdu and Hindi at all hours. Before the agents median first response were 15.4 minutes, price questions were answered with “the team will call you” by policy, and free site visits offered eagerly, of which roughly 8 in 10 produced no job.
Now the line runs 24/7 on a master workflow of about 79 nodes plus tool sub-workflows, self-hosted in Docker. Gemini for the agent and media analysis, Chatwoot for the human side (think about it as a Whatsapp Interface forthe organization), live price catalog in a table the agent must call, Zoho Books estimates are created as drafts only, always reviewed by a human before sending. Measured from the full conversation log over 8 months: median first response reduced to 13 seconds, 82% of all customer messages answered within 60 seconds, and price questions now get a real AED figure in chat instead of deflection.
The design rule that made it safe to run against real customers: behaviour lives in the prompt, guarantees live in the graph. Tone, sales ladder, language matching are prompt, cheap to iterate. Spam mutes, bot pause on complaints, and “no catalog row, no price, not even a range” are workflow gates that cannot be sweet-talked. That last rule exists because the model once invented a crane fee that was not there in cleint’s knowledge base.
Three things that broke in production, none of them are the model:
- An unpinned Docker image upgraded n8n on restart and moved Code node execution to the external task runner, where one cross-node data access pattern can block indefinitely. Messages hung for minutes, twice. Fixed by rewriting that node as declarative expressions and pinning the image. I also added a 180 second workflow timeout, and it is the only reason we found the root cause: cancelled runs save no execution trace, so the timeout converts “hangs until someone kills it” into “dies with a body you can autopsy.”
- A misclassification silently muted a live lead. One message mentioning a pending receipt got labelled VENDOR, and a gate then dropped every later message from that customer, including “I need a quotation for grass,” while reporting success. Now only spam can hard-mute, every drop leaves a visible note, and payment language can never classify as vendor.
- A “hallucinated” price that mostly wasn’t. The figure came from a stale legacy catalog row, surfaced because an earlier table read had been silently truncated at 60 of 342 rows. Lesson: verify your observability before blaming the model. It will faithfully quote whatever the tool returns.
Before any change ships, a regression harness replays a golden dataset built from real conversations and scores the output: price given without deflection, correct escalation, tone, no premature visit offer. The harness stops before the send step, so evaluation never messages a real customer. It is public, with the dataset and scores: GitHub - xaphor/mira-whatsapp-agent-eval: Scored evaluation harness for a production WhatsApp sales agent: rubric, golden dataset, regression gate · GitHub . Full case study with the architecture diagram and the complete failure catalog: From 15.4 minutes to 13 seconds: a production WhatsApp sales agent for a UAE landscaping business | case-studies
I am not posting the master workflow itself, it is the production brain of a paying client. But I am happy to extract the generic parts as clean templates if there is interest: the WhatsApp burst debounce with media aggregation, the error-to-WhatsApp alert workflow, or the eval harness pattern on Data Tables. Tell me which one would actually help you and I will build that one first.
One question for people running conversational agents here: rapid-fire messages each spawn their own execution, which produces out-of-order replies. It is my last unsolved production bug and I think it needs a per-conversation queue. Has anyone built a clean pattern for this in n8n without an external broker?