Module 6: Real Channels: Web Chat, WhatsApp, Telegram, and Voice
6. Conversational UX per channel: asynchrony, limits, and buttons
Description
By the end of this lesson you'll be able to adapt your agent's same response to four channels with explicit criteria instead of intuition: handle messaging's asynchrony and the problem of a message arriving mid-reasoning, say "I'm thinking" in each channel's own language, split messages that exceed hard limits, apply the right formatting on each one, and design buttons and quick replies with the rules that make people actually use them. And you'll know where each of those adaptations lives, which is the question that separates a maintainable system from a prompt full of patches.
This matters because you've reached the point where you have four channels working with the same agent behind them — and where half the things that look wrong aren't agent errors. A message arriving with asterisks in plain view, a nine-paragraph response that's a wall of text on a phone, fifteen seconds of silence with no sign of life, a seven-option menu nobody reads: none of that shows up as a failed execution in n8n. Everything looks green. And all of it makes people stop using the agent. A chatbot's perceived quality gets decided at this layer far more than it gets decided in the prompt.
Connection to the module: you're coming from four channel lessons, each with its own scattered formatting traps — Telegram's Parse Mode, WhatsApp's asterisks, the impossibility of reading a URL over the phone. This lesson brings all of that together into a method, and it does so right before lesson 7 for a reason: first you need to know what gets adapted before you can decide where that adaptation lives. Lesson 7 builds the architecture; this one defines what it has to do. Module 5's brain still, once again, doesn't get touched — with one bounded exception you're going to discuss at the end.
The same news, four ways of delivering it
Think about having to tell a TuTienda customer their order arrived late and the replacement has already shipped. It's a single piece of news. Now imagine delivering it four different ways.
By letter, you write full paragraphs. You explain what happened, why, what got done, and close with a formal apology. Whoever reads it has all the time in the world and can re-read it. Nobody complains that a letter is long.
In a hallway, running into the person, you have twelve seconds. You say the essentials: "yours got delayed, the replacement already shipped, arrives Thursday." If you started explaining internal logistics, the person would walk off halfway through.
By text message, the person might read it now or in three hours. That changes two things: the message has to be self-sufficient — you can't count on them remembering the context — and it has to be short, because they're going to read it on a phone, probably while walking.
By phone, they can't re-read anything. You say one thing, wait, say the next. And you have to confirm they understood, because there's no way for them to check back.
The news is the same in all four cases. The facts are identical, the company's policy is identical, and the information conveyed at bottom is the same. What changes is the shape of the conversation, and that shape isn't decided by whoever has the news: it's decided by the medium.
Everything that follows turns that intuition into five configurable axes.
The five axes
| Axis | The question it answers | Where it gets resolved |
|---|---|---|
| 1. Pacing | How much time can pass between one turn and the next? What happens if two messages arrive in a row? | Input adapter |
| 2. Waiting | How do I say "I'm thinking" on this channel? | Output adapter |
| 3. Length | How much text does this channel tolerate, and what do I do if I go over? | Output adapter |
| 4. Format | What markup does it understand and what breaks the message? | Output adapter |
| 5. Interaction | Can I give buttons? How many? What do I do if I can't? | Output adapter + brain |
Let's go through all five one by one. And notice the last column from the start: four of the five live entirely in the adaptation layer. Only the fifth touches the brain, and at a very bounded point.
Axis 1 — Pacing: synchronous, asynchronous, and the message that arrives late
There's a difference between channels that gets underrated and that changes the design more than any other: how much time can pass between one turn and the next without the conversation breaking.
Web chat Live session. The person is looking at the screen RIGHT NOW.
If they close the tab, the conversation is over.
A turn's half-life: seconds.
WhatsApp Genuinely asynchronous. The person asks and leaves.
Telegram They reply in three hours, or tomorrow, or Monday.
The conversation doesn't break: it's still there.
A turn's half-life: hours or days.
Voice Hard real time. A two-second silence is already
uncomfortable; a five-second one is a dropped call.
A turn's half-life: hundreds of milliseconds.
That table's practical consequences are three, and all three are design decisions, not plumbing ones.
First: in messaging, every message has to be self-sufficient. If the agent writes "which of the two?" and the person reads that three hours later, with twenty notifications in between, they have no idea what it's about. The version that works is "which of the two orders do you want to check, #4521 or #4498?" It costs eight more words and avoids a whole clarification turn. It's a rule that isn't needed in web chat and is essential on WhatsApp.
Second: the agent can't treat a conversation as dead because of silence. In web chat, if ten minutes pass with no reply, the person left. On WhatsApp, no: they'll probably come back. A flow that "closes" the conversation after five minutes of inactivity works on one channel and is a mistake on the other.
Third, and the most problematic: the message that arrives mid-reasoning.
The rapid-message problem
This is a real problem almost nobody anticipates and that shows up on day one of use.
t=0s Customer: "hi"
t=0s n8n triggers execution 1. The agent starts reasoning.
t=2s Customer: "I wanted to ask about my order"
t=2s n8n triggers execution 2. ANOTHER agent instance starts.
t=4s Customer: "the 4521"
t=4s n8n triggers execution 3.
t=7s Execution 1 finishes → sends "Hi! How can I help you?"
t=9s Execution 3 finishes → sends "Your order 4521 is on its way…"
t=11s Execution 2 finishes → sends "Sure, could you give me the order number?"
The customer gets three messages, the last of which asks for a piece of data they already gave, and in an order that doesn't make sense. It looks exactly like a broken bot. And in n8n all three executions came out green: nothing failed.
The cause is that a webhook triggers an independent execution for every message, and executions don't know about each other. People, meanwhile, write the way they talk: in short bursts. On WhatsApp this is the norm, not the exception.
There are three solutions, with different costs:
Solution 1 — Group by wait time (the most used). Instead of processing every message as it arrives, they accumulate and a couple seconds of silence get waited out before sending them together to the agent. This is what's called a debounce in programming.
# Silence-based grouping pattern
WhatsApp Trigger
│
├─► Redis: append the text to a list with key = phone number
│
├─► Wait: 3 seconds
│
├─► Redis: read the whole list and check whether it changed
│ did more messages arrive while waiting?
│ ├── yes → this execution ends here (the last one handles it)
│ └── no → continue
│
├─► merge the messages into a single text
│ "hi\nI wanted to ask about my order\nthe 4521"
│
└─► AI Agent (once, with the whole context)
The result is an agent that responds once, with everything the person meant to say. It's more work and it's worth it: it's the difference between a bot that feels attentive and one that feels overwhelmed. It requires storage shared between executions — Redis is the natural choice, but a Postgres table works just as well.
Solution 2 — A lock per conversation. Mark the conversation as "busy" while the agent reasons, and have executions that arrive meanwhile queue their text instead of starting another agent. It's more robust and considerably more complex.
Solution 3 — Accept it and mitigate with speed. If your agent responds in two seconds, the collision window is small. It's the honest option for a project in the learning phase, and with Module 5's multi-agent system — which takes between eight and fifteen seconds — it isn't enough.
For lesson 8's mini-project it's enough to recognize the problem and document it. For a real WhatsApp system, solution 1 isn't optional.
Axis 2 — Waiting: how each channel says "I'm thinking"
You already saw pieces of this across three different lessons. Here it is together:
| Channel | How waiting gets signaled | How long it lasts |
|---|---|---|
| Web chat | The widget's three dots, automatic; or Response Mode: Using Response Nodes with an intermediate message; or streaming | For as long as the execution lasts |
| No native indicator from the API. Solved with a real text message | However long the message takes | |
| Telegram | Send Chat Action with the typing action | About five seconds; can be repeated |
| Voice | A spoken phrase while the tool runs (Speak During Execution and equivalents) | However long the phrase lasts |
And the rule that comes out of the table, which is simple and gets ignored a lot: if your agent takes more than three seconds, it has to say something. It doesn't matter which channel; what changes is the mechanism.
One detail about the waiting message that does matter: it has to be specific, not generic. Compare:
❌ "One moment…"
✅ "Let me check your order, give me a few seconds."
The second one confirms the agent understood the question, which the first doesn't. If the agent misunderstood, the person finds out now and can correct it, instead of waiting ten seconds to discover it. It's free information tucked into a message you were going to send anyway.
Axis 3 — Length: hard limits and patience limits
There are two kinds of limit and it's worth not confusing them.
Hard limits belong to the platform. If you go over, the message fails:
WhatsApp 4096 characters in a text message's body
Telegram 4096 characters per message
64 bytes in a button's callback_data
Web chat no relevant hard limit
Voice not applicable: there are no characters, there are seconds
Patience limits belong to people, and they're much lower:
Web chat a paragraph or two read without a problem
WhatsApp more than four or five lines and people stop reading
Telegram same as WhatsApp
Voice two or three sentences; beyond that, information isn't retained
The hard limit produces a visible error you fix once. The patience limit produces an agent people abandon without saying why, and that one doesn't show up in any trace.
For the hard limit, the fix is splitting the message in the output adapter:
# Node: Code — Name: split_long_message
# Splits the text into chunks under the channel's limit, cutting at
# paragraph breaks when possible and at spaces as a fallback.
// Never cut mid-word: it looks like a system error.
const MAX = 4000; // safety margin under 4096
const text = $json.output;
const chunks = [];
let current = '';
for (const paragraph of text.split('\n\n')) {
// If adding this paragraph goes over the limit, close the current chunk.
if ((current + '\n\n' + paragraph).length > MAX && current) {
chunks.push(current);
current = paragraph;
} else {
current = current ? current + '\n\n' + paragraph : paragraph;
}
}
if (current) chunks.push(current);
// One item per chunk: the sending node sends them in order.
return chunks.map(chunk => ({ json: { text: chunk } }));
For the patience limit, the fix isn't splitting: it's generating less text. And that does touch the brain, because how much the agent says is a content decision. It's the exception discussed further below.
Axis 4 — Format
Here's the table that solves 90% of messages that look amateurish:
| Bold | Italics | Links | Notes | |
|---|---|---|---|---|
Web chat (@n8n/chat) | **text** | *text* | Normal Markdown | Interprets Markdown; the most forgiving channel |
*text* (single asterisk) | _text_ | Plain-text URL, gets auto-detected | **text** shows up with the asterisks | |
| Telegram | Requires Parse Mode | Requires Parse Mode | Get auto-detected with no parse mode | MarkdownV2 requires a lot of escaping; HTML is safer |
| Voice | Doesn't exist | Doesn't exist | Never read aloud | Any markup gets read or handled unpredictably |
Notice the table's central trap: the same asterisk means different things. A language model writes standard Markdown by default — double asterisk for bold — because that's what it saw in training. That looks fine in web chat and bad on WhatsApp, where the standard is a single asterisk.
The conversion is one line in the output adapter:
# Node: Code — Name: markdown_to_whatsapp
// The agent writes standard Markdown; WhatsApp uses its own markup.
// We convert HERE, we don't ask the agent to write differently.
let text = $json.output;
text = text.replace(/\*\*(.+?)\*\*/g, '*$1*'); // **bold** → *bold*
text = text.replace(/^#{1,6}\s+/gm, ''); // strip Markdown headers
text = text.replace(/^[\-\*]\s+/gm, '• '); // bullets → middle dot
return [{ json: { text } }];
Three lines of replacement and the problem disappears forever, in a place where you can see it and test it. The alternative — asking the agent in its prompt not to use double asterisks — fails intermittently, spends the model's attention, and has to be repeated in every agent you add.
For voice, the cleanup is more aggressive: strip all markup, turn URLs into an offer to send them through another channel, and turn numbers and dates into their spoken form. A good chunk of that is better done in the data the tool returns, as you saw in lesson 5.
Axis 5 — Buttons and quick replies
Buttons are the best conversational UX tool there is, for a reason that goes beyond convenience: every button is one less ambiguity for the agent. A customer who writes "the headphones one, I think" forces the model to interpret. A customer who taps a button hands over exact data. Buttons don't just improve the experience: they improve the system's accuracy.
What each channel offers:
| Channel | Mechanism | Typical limits |
|---|---|---|
| Web chat | Chat node with Send and Wait for Response, Approval type | Approve / reject, with customizable labels |
| Interactive messages: reply buttons and list messages | On the order of 3 buttons, or a list with more options | |
| Telegram | Inline keyboard with callback_data | Practically no limit on count; there is one on data length |
| Voice | They don't exist. Replaced with spoken options | Three at most, said in one sentence |
Worth repeating lesson 3's warning about WhatsApp: the platform supports interactive messages, but the level of native support for building them from the node varies between n8n versions, and in some cases you have to build the body with an HTTP Request against Meta's API. Open the message-type selector on your node and confirm what your version offers before designing a flow that depends on buttons.
Five button rules that hold across all four channels
Rule 1 — Three options, or a list. More than three buttons on a phone screen reads like a form and people stop reading. If there genuinely are seven options, they're not seven buttons: it's a poorly framed question that's worth splitting into two, or a list message.
Rule 2 — Always a way out. Every button menu needs an "it's something else" or "talk to a person" option. Without it, whoever doesn't fit any option gets stuck — and a lot of people don't type free text when they see buttons, because buttons read as the only available options.
Rule 3 — A button's text is an answer, not a category. Compare "Billing" with "I have a charge I don't recognize." The second one gets picked without thinking; the first forces you to mentally translate your own problem into the company's jargon.
Rule 4 — The hidden data is short and stable. order:4521, not a JSON. Telegram has a 64-byte cap and other channels have similar caps. And don't put sensitive data there: it travels back from the client.
Rule 5 — In voice, buttons get spoken and mentally numbered, not listed. "Is it about an order, a charge, or something else?" — three options, in one sentence, in the order they're going to be remembered. Never "press one for…", which is a nineties phone menu and exactly what an AI agent exists to replace.
Worked example: one response, four channels
TuTienda's triage_agent resolved a case with two topics. Its raw output, as it would write it with no adaptation at all:
Hi, Ana! I checked both things you asked about.
**About the $1,200 charge from July 18th:** it doesn't match any of
your purchases in the last 60 days, so we opened dispute **#D-8842**.
The billing team reviews it within a maximum of 48 business hours and
we'll let you know through this same channel.
**About order #4521:** it left the distribution center on July 21st
and the estimated delivery is July 23rd. You can track it here:
https://tutienda.example/track/4521
Anything else I can help with?
Now the four channels.
Web chat. It goes out as-is. The widget interprets Markdown, the bold text shows up, the link is clickable, and whoever's reading is looking at the screen. This is the channel where the agent can write however it wants. No adaptation.
WhatsApp. The formatting gets converted and it gets shortened. Four paragraphs on a phone are a wall:
Hi, Ana! I checked both things.
*$1,200 charge from July 18th:* doesn't match any of your purchases,
so we opened dispute *#D-8842*. We'll let you know here within a
maximum of 48 business hours.
*Order #4521:* on its way, arrives July 23rd.
Tracking: https://tutienda.example/track/4521
Anything else?
What changed: double asterisk to single, the dispatch date got removed (a process detail, not a value one), "distribution center" got removed (internal jargon), and the closing went from seven words to two. The link stays: on WhatsApp links work and are useful.
Telegram, with buttons. Here what the channel allows gets put to use:
Text (no Parse Mode, plain text):
Hi, Ana! I checked both things.
$1,200 charge from July 18th: doesn't match any of your
purchases. We opened dispute #D-8842 and we'll let you know
within a maximum of 48 business hours.
Order #4521: on its way, arrives July 23rd.
Inline Keyboard:
Row 1: "See order tracking" → url: https://tutienda.example/track/4521
Row 2: "See dispute details" → callback_data: "dispute:D-8842"
Row 3: "I need something else" → callback_data: "menu:other"
Notice two decisions. The link came out of the text and became a button — a URL-type button, which Telegram supports — which cleans up the message and makes the action more obvious. And there's an explicit way out in the third row, following rule 2.
Voice. It gets completely rebuilt:
Turn 1:
"I checked both things. The twelve-hundred-dollar charge doesn't
match any of your purchases, so we already opened a dispute and
the team reviews it within a maximum of two business days."
[pause — wait for a reaction]
Turn 2:
"And about your order: it's on the way and arrives Thursday.
Want me to text you the tracking link?"
What changed: a single topic per turn with a pause in between, the dispute number disappeared (nobody retains #D-8842 by ear, and if they need it, it gets sent in writing), "48 business hours" became "two business days," which is how people talk, the date became "Thursday," and the URL turned into an offer to send it through another channel.
Four versions. One single agent. None of the four adaptations required changing triage_agent's prompt.
Where the adaptation lives, and the only exception
The general rule is lesson 1's: format belongs to the adapter, content belongs to the brain. Converting asterisks, splitting messages, turning a link into a button, cleaning up markup for voice — all of that is transformation over an already-generated text, and it lives in the output layer where it can be seen, tested, and fixed in one single place.
But look at the worked example again and you're going to notice something uncomfortable. The voice version isn't a transformation of the web chat version. There's no function that turns four bold-laden paragraphs into two two-sentence turns without losing what matters. The WhatsApp version is right on the border: removing the dispatch date is a decision about which information is worth keeping, not about how it's written.
That means the length axis does touch the brain. And there's an honest way to resolve it without filling the prompt with channel rules: pass the agent a channel variable, and have it modulate only its verbosity.
# triage_agent's System Message — the ONLY channel-aware fragment
You're going to receive a `channel` field indicating which channel
the message arrived through. Use it only to decide HOW MUCH text
to write, never to change your response's format or content:
- web: you can write up to three paragraphs.
- whatsapp: four lines maximum. One topic per message.
- telegram: same as whatsapp.
- voice: two or three sentences. A single topic per turn. When
you finish a topic, pause and wait before moving on to
the next.
Don't change the text's format based on the channel: always write
standard Markdown. The system does the conversion, not you.
That last sentence is what makes the design hold up. The agent decides how much, the adapter decides how it looks. Each layer makes the decision that's its own, and the prompt doesn't grow when you add another channel — at most, it gains one line in that list.
It's worth acknowledging the cost of this decision, because it isn't free. The brain stops being completely channel-agnostic. If tomorrow you add a new channel with another name and don't update that list, the agent falls into undefined behavior. And those four lines occupy context on every turn.
The alternative would be maintaining separate prompt versions per channel, which is worse for everything you already saw in lesson 1: diverging prompts. Or having the agent always write short, for the worst channel — which wastes the web chat, where a complete explanation is exactly what people want.
Among the three, a channel variable that only modulates length is the best known balance. It's a good example that clean architecture doesn't always win: what wins is the one with the lowest total cost, and knowing how to justify why a specific impurity got accepted conveys considerably more judgment than defending a rule with no exceptions.
Common mistakes
Asking the agent to handle formatting (conceptual). What happens: the message looks bad on WhatsApp, and the instinctive fix is adding "don't use double asterisks" to the system prompt. It works a handful of times and fails intermittently, because a model writes Markdown out of habit. Then Telegram arrives, another line; then voice, three more. The prompt fills up with presentation rules. Why it happens: it's the thirty-second fix and the first attempt works. How to spot it: if any agent's system prompt mentions a character, a formatting mark, or a channel's name outside the verbosity list, this is it. How to fix it: move the conversion to a Code node in the output adapter. Three regular-expression replacements solve WhatsApp entirely, get tested in two minutes, and never fail.
Ignoring rapid-fire messages (practical). What happens: the agent works perfectly in testing — where you type one complete sentence and wait — and in production it sends duplicate, out-of-order responses, and asks for data the customer already gave. Why it happens: people write in bursts of three or four short messages, each one triggers an independent execution, and executions can't see each other. It's the norm on WhatsApp. How to spot it: look at the execution log filtered by a single customer; if you see three executions less than five seconds apart, this is it. How to fix it: the silence-grouping pattern, with Redis or a table, which waits a couple seconds before sending everything together to the agent. If you're not going to implement it yet, at least document it as a known limitation — it's infinitely better than discovering it with real customers.
A seven-button menu (conceptual). What happens: someone maps the support department's seven categories to seven buttons, and most people ignore the menu and type free text anyway, which cancels out the entire benefit. Why it happens: the seven categories exist in the organization, so exposing them seems natural. How to spot it: if fewer than half of people use your buttons, the menu is too long or the labels are in internal jargon. How to fix it: three options maximum, written the way the customer would describe their problem and not what the company calls it, plus an explicit way out. If seven really are needed, it's a question worth splitting into two levels — but first it's worth asking whether the agent can classify on its own, which is what it's there for.
Splitting messages by cutting mid-word (practical). What happens: the response exceeds 4096 characters, a Code splits it every 4000, and the customer gets one message ending in "the order is" and another starting with "on its way." It looks worse than if the message had failed. Why it happens: splitting by character count is the one-line implementation, and it works until a cut lands inside a word. How to spot it: deliberately send a query that generates a long response and look where it cuts. How to fix it: cut at paragraph breaks first, at spaces as a fallback, and never mid-word. And before that, ask why the agent wrote 4000 characters: the real problem is almost always verbosity, not splitting.
A generic waiting message (practical). What happens: the agent sends "One moment…" and twelve seconds later responds with something unrelated to what the customer asked, because it misunderstood from the start. Twelve seconds wasted. Why it happens: the waiting message gets written as fixed text, without using what's already known about the query. How to spot it: read your waiting message; if it works equally well for any question, it's generic. How to fix it: have the message repeat what the agent understood — "let me check your order," "I'll look into that charge." It confirms comprehension and gives the chance to correct it before spending the twelve seconds. It's free information in a message you were going to send anyway.
Exercises
Exercise 1 — Adapt a hard response. TuTienda's billing_specialist reports the dispute got resolved against the customer: the charge was legitimate, it matches a subscription the customer activated three months ago, and there won't be a refund. triage_agent has to communicate it. Write the response for WhatsApp (four lines maximum, with buttons if your version supports them) and for voice (two turns). Then write one line about what's hardest to adapt in bad news.
See solution
WhatsApp:
Ana, we already reviewed dispute #D-8842. The $1,200 charge matches
your Premium subscription, active since April 15th, so no refund
applies.
I understand this isn't the answer you were hoping for. I can help
you cancel the subscription if you no longer want it.
[Cancel the subscription] [Talk to a person]
Voice:
Turn 1:
"Ana, we already reviewed your case. The twelve-hundred-dollar
charge matches your Premium subscription, which has been active
since April, so no refund applies."
[pause — let them react]
Turn 2 (depending on what they say):
"I understand. If you no longer want the subscription I can cancel
it right now, or connect you with a team member. Which would you
prefer?"
The hardest thing to adapt in bad news is the pacing, not the words. On WhatsApp you can give the refusal and the way out in the same message, because the person reads them together and decides on their own time. Not in voice: if you chain the refusal to the offer with no pause, it reads as if you're selling something to someone you just told no. Turn 1's pause isn't a technical channel limitation — it's what makes the response sound human.
Two decisions worth flagging. The refusal goes first and straight, on both channels: beating around the bush before bad news reads as evasive. And both versions offer a concrete way out — cancelling, or talking to someone — because a refusal with no possible action is where a conversation turns into a complaint.
Why it works: bad news is where conversational design genuinely gets tested. Anyone can deliver good news well.
Exercise 2 — Design the grouping. Write out the silence-grouping flow for WhatsApp: what nodes you'd use, what you store and under what key, how long you wait, and how an execution decides whether it's its turn to process or step aside. Then answer: what happens if the customer sends a message while the agent is already reasoning, after grouping already closed?
See solution
The flow:
WhatsApp Trigger
│
├─► Redis: RPUSH key = "buffer:{{ phone }}" value = the text
├─► Redis: get the list's length → save it as len_before
│
├─► Wait: 3 seconds
│
├─► Redis: get the length again → len_after
│
├─► IF: len_after > len_before
│ ├── yes → NoOp. More messages arrived; the last execution
│ │ will handle all of them. This one steps aside.
│ └── no → continue
│
├─► Redis: read the whole list and DELETE it
├─► Code: merge the texts with line breaks
│
└─► AI Agent → respond
The key is the customer's phone number, because grouping is per conversation: two different customers writing at the same time should never mix together.
The three seconds are a compromise. Less than two and you miss slow bursts; more than five and you add perceptible latency to every conversation, including the ones from people who wrote a single message. It's worth measuring with real traffic instead of guessing.
And the second question, which is the interesting one. If the customer writes while the agent is already reasoning, grouping doesn't cover it: that new execution starts its own three-second cycle and ends up triggering a second agent on the same conversation. You're back in the original problem, just with a smaller window.
Grouping reduces the problem a lot and doesn't eliminate it. To eliminate it you need solution 2 — a lock per conversation that prevents two agents from running at once on the same customer, queuing whatever arrives meanwhile. It's worth knowing that's the complete solution, and also that in most projects grouping alone covers enough that the extra complexity isn't worth it. Explicitly acknowledging that limit — "this covers 90% of cases, the remaining 10% needs a lock and I didn't implement it because…" — is better engineering than implementing the lock without having needed it.
Why it works: the exercise ends at a known limit instead of at a perfect solution, which is how almost all real concurrency problems look.
Exercise 3 — Audit your four channels. Take three real responses from your TuTienda agent — one short, one long, and one with a refusal — and run them through this checklist on every channel you have set up. Note how many boxes fail.
- The formatting looks the way it should (no visible asterisks, no markup read aloud).
- No response exceeds the channel's hard limit.
- No response exceeds the patience limit (four lines in messaging, two sentences in voice).
- If the agent took more than three seconds, there was a waiting signal.
- The waiting signal was specific, not generic.
- Every message is self-sufficient: it makes sense read three hours later.
- If there are buttons, there are no more than three and there's an explicit way out.
- In voice, no URL and no long identifier got spoken.
See solution
There's no single answer, but there's a pattern that repeats so often it's worth anticipating: almost everyone fails the same three boxes.
The waiting-signal one, because during development you watch the execution in n8n and don't perceive the silence the customer perceives. It's the one with the most impact for how little it costs to fix.
The self-sufficiency one, because you test by typing and answering immediately, never with three hours in between. It gets detected by re-reading the agent's responses without looking at the question: if any of them says "which of the two?" without saying which two it means, it already failed.
And the patience-limit one, because on the wide screen where you develop, four paragraphs look reasonable. On a phone they're a screen and a half of scrolling.
One piece of advice on doing this audit right: do it from a real device, not from n8n's execution log. Send the three questions from your own phone and read them the way a customer would. Seeing the text in an execution's JSON and seeing it arrive as a notification on a phone are different experiences, and only the second one tells you the truth.
Why it works: the list turns "the bot feels off" — which is what people report and which you can't do anything with — into eight concrete boxes, each with a known fix.
Summary and next step
You now have the method for adapting a conversation to its channel, in five axes. Pacing forces you to write self-sufficient messages in messaging and to solve the burst problem, which is real and shows up on day one. Waiting has a different mechanism per channel and a common rule: over three seconds requires saying something, and that something should be specific. Length has hard limits that break the message and patience limits that break the product, and only the first ones get solved by splitting. Format gets converted in the adapter with three replacements, never by asking the agent. And button interaction isn't just convenience: every button is one less ambiguity, with five rules that hold across all four channels.
And you've located the only legitimate exception to the layer separation: a channel variable the agent uses to modulate how much text it writes, never how it looks. The agent decides how much, the adapter decides how. Knowing, on top of that, what that impurity costs and why it's accepted.
Before moving on you should be able to: explain why the same asterisk looks fine on one channel and bad on another, and where it gets fixed; describe the burst problem and why grouping reduces it without eliminating it; and name the five button rules without looking.
What you still don't have is where to put all of this. You have four channels, five adaptation axes, and a brain that shouldn't find out about any of it — and if you implement it channel by channel you end up with four copies of the logic, which is exactly the mistake lesson 1 flagged. Lesson 7 builds the architecture that solves it: a single core in a sub-workflow, thin adapters per channel, and a data contract between them as explicit as Module 5's contract between agents.
Resources
- Chat node — n8n Docs — the web channel's intermediate messages and approval buttons, with their response types.
- Telegram node — n8n Docs — the inline keyboard, URL-type buttons, and the
typingchat action. - WhatsApp interactive messages — Meta for Developers — the structure of buttons and lists, especially useful if your node's version doesn't expose them and you have to build them with
HTTP Request. - Formatting options — Telegram Bot API — the list of characters that need escaping in each parse mode, and the reason this lesson recommends plain text.
- Redis node — n8n Docs — the storage shared between executions that makes the silence-grouping pattern possible.
- Wait node — n8n Docs — the grouping's pause; worth understanding how it behaves with many parallel executions before taking it to production.
- Code node — n8n Docs — where format conversions and long-message splitting live, with its one-item-per-returned-element mode.