Building the Bridge: from Dify to Chatwoot
I spent a weekend wiring an AI bot into a customer support inbox, and the part that ate most of my time was not the AI and not the inbox. It was the small piece of software that sits between them. Nobody talks about that piece much, which is strange, because it is the piece that makes the whole thing work. Here is how it went.
The setup is common enough. On one side you have a help desk that collects conversations from a website widget and a messaging channel. On the other you have an AI platform that holds your knowledge base and answers questions. Both are mature products. Both have APIs. You would assume someone has already glued them together in a clean, maintained package.
They have not, or at least not in a way I would trust in production. What I found instead were a couple of community projects that looked promising for about ten minutes. One was still in beta and had not been touched in a long while. Its companion repository was gone entirely. Another dragged in a heavy stack of background workers and an extra database just to pass a message from one place to another. For a job this simple, that felt wrong.
What the middleware actually does
Strip away the jargon and the job is almost boring. A customer sends a message. The help desk pokes your service to say a message arrived. Your service reads it, asks the AI for an answer, and posts that answer back into the conversation. That is the entire loop.
The first instinct is to write the part that calls the AI. Resist it. The first thing your service should be good at is saying no. A help desk fires events for all kinds of reasons, and most of them are not your concern. The bot's own replies generate events too, and if you are not careful your service will answer itself in a loop that is both funny and expensive.
I settled on a tight filter near the top of the request handler. Act only on genuinely new messages from the customer, and only while the conversation is still in the bot's hands. Everything else gets a polite acknowledgment and nothing more. Getting this filter right early saved me from a whole category of bugs that would have been miserable to chase later.
There is a timing trap here that is worth naming. The help desk expects a quick reply when it pokes your service. The AI, meanwhile, can take several seconds to think, especially when the answer runs long. If you make the help desk wait for the AI, you risk the request timing out, and timeouts in this kind of plumbing are the worst sort of intermittent problem.
The fix is to acknowledge the poke immediately and do the real work in the background. Tell the help desk you received the message, close that request, and only then go bother the AI. The customer waits a few seconds for a thoughtful answer, which is fine. Your service never leaves anyone hanging on an open connection.
An AI answer is much better when it knows what was already said. Most platforms give you a handle, some kind of identifier, that ties a series of messages into one ongoing conversation. Your job is to remember which platform conversation belongs to which help desk conversation, and to hand that identifier back each time.
You do not need much to store this. A tiny local database file, the kind that lives on disk and needs no server of its own, is plenty. People reach for a full database engine by reflex, but for a lookup table this small that is overkill. Keep the storage as light as the job demands.
A bot that cannot get out of the way is worse than no bot. Build the exit early. There should be a few clear ways a conversation leaves the bot and reaches a person. The customer asks for a human in plain language. The AI signals that it is out of its depth. Or the AI simply fails, in which case a human should catch the conversation rather than the customer hitting a wall.
Mechanically the handoff is just flipping the conversation's state so the bot stops paying attention and the team can pick it up. The hard part is not the mechanics, it is remembering to treat failure as a normal path rather than an afterthought. When the AI errors out, the customer should glide to a person, not see a broken silence.
Even if you start with a single bot on a single channel, leave room for more. The cheap way to do this is a small routing table that maps each inbox to the AI app that should serve it. One channel today, three channels and three different bots tomorrow, and the routing table absorbs the change without you rewriting the core. This costs almost nothing to set up on day one and saves a real rewrite later.
The part I want to be honest about is that the clean architecture was not what cost me the night. The surprises were. A platform update had quietly added a security rule that refused to deliver messages to internal network addresses, which is exactly where a service like this tends to live. The behavior looked like the bot was broken when in fact the message was being blocked one step upstream, by a protection that was doing its job a little too eagerly.
None of these were architecture problems. They were the kind of thing you only find by reading logs slowly and refusing to guess. When the bot goes quiet, the logs are already telling you why. Trace the message one hop at a time and let the system show you where it stopped.
The bridge is small. It is also the difference between two impressive tools that ignore each other and one quiet system that answers your customers at three in the morning. Worth building carefully.
Member discussion