Skip to content

chatwoot-adapter 0.5.5: id-less own sends, and out of beta#44

Merged
rmyndharis merged 2 commits into
mainfrom
fix/chatwoot-idless-own-send
Jul 22, 2026
Merged

chatwoot-adapter 0.5.5: id-less own sends, and out of beta#44
rmyndharis merged 2 commits into
mainfrom
fix/chatwoot-idless-own-send

Conversation

@rmyndharis

Copy link
Copy Markdown
Owner

The fix

When WhatsApp does not return an id for a message you send — occasional, and more common on Baileys — handleSent still used that missing id as its de-duplication key. Every id-less own send therefore collapsed onto a single marker: the first reached Chatwoot, and any later one was skipped as "already seen" for the marker's full three-day retention. Silently, and only in the Chatwoot thread; the message itself reached the recipient normally.

An id-less message cannot be de-duplicated by definition, so the choice is which way to fail. It is now relayed with both marker operations skipped and the skip logged: a duplicate in the helpdesk is visible and harmless, a missing customer message is neither. outbound.ts already treats the same empty-id case as a real hazard for its own echo guard; this brings sent.ts in line.

The test drives two id-less own sends and asserts both are mirrored. It fails against the previous commit.

Out of beta

The promotion rests on evidence, not elapsed time. Exercised end to end against a live WhatsApp account on both engines:

Path Result
Echo loop (own send mirrored, not re-sent) verified on Baileys and whatsapp-web.js
Agent replies from Chatwoot, text and media verified
History backfill, 50 messages imported, photos with files intact, nothing re-sent to WhatsApp
Unit suite 406/406

The backfill run matters most here: it replays fromMe history as outgoing through the same code path that carried the duplicate-send defect, and it had never been executed even once before now.

Two blockers that did not survive checking

Both are recorded because the reasoning is more useful than the conclusion.

An own send that never reached Chatwoot looked like a mirroring gap at roughly 2% of outgoing traffic. It was a send that had failed outright — status failed, never delivered — so there was correctly nothing to mirror. The measurement had counted failed sends.

A batch of imported messages appeared as empty bubbles, ~14% of the import. They were captionless photos: empty content, one image attachment each, files intact at 36 KB–349 KB. That is exactly what a photo without a caption should look like. Acting on that reading would have added placeholder text to images that render correctly today.

Known limitation, now in the README

A contact arriving privacy-id-first, or one whose stored mapping is lost, can appear as a second Chatwoot contact. It costs no messages, and cannot affect a thread already mapped — once a chat is mapped the adapter never creates a second conversation for it. Merging the contacts in Chatwoot is safe. It previously lived only in code comments; stable should mean the edges are known, not absent.

Catalog regenerated; tsc --noEmit clean.

An engine that cannot read a message id back reports the empty sentinel — Baileys'
`?? ''`, a case outbound.ts already logs for its own echo guard, and one the
gateway's unique index sidesteps because '' would collide.

handleSent keyed its de-duplication on that id unconditionally, so every id-less
own send collapsed onto one marker: the first was mirrored into Chatwoot, and
every later one was skipped as "already seen" for the marker's full three-day
retention. Silently, and only in the operator's helpdesk thread.

An id-less message cannot be de-duplicated by definition, so the only question is
which way to fail. Relay it and skip both marker operations: a duplicate in the
helpdesk is visible and harmless, a missing customer message is neither. The skip
is logged.

Found while investigating an own send that never reached Chatwoot. That one turned
out to be unrelated — the send had failed outright, so there was correctly nothing
to mirror — but the path it led through was genuinely broken. The test drives two
id-less sends and asserts both are mirrored; it fails on the previous commit.
Ships the id-less own-send fix from the previous commit and moves the plugin out
of beta.

The promotion rests on evidence rather than elapsed time. The two-way relay was
exercised end to end against a live WhatsApp account on BOTH engines — inbound
relay, agent replies with text and media, own outbound messages mirrored without
being re-sent, and a real 50-message history import that imported photos with
their files and re-sent nothing. Tested against OpenWA 0.10.5.

Two things I expected to block this did not survive checking, and both are worth
recording. An own send that never reached Chatwoot turned out to be a send that
had failed outright, so there was correctly nothing to mirror. And a batch of
imported messages that looked like empty bubbles were captionless photos: empty
content, one image attachment each, files intact — which is what a photo without a
caption should look like.

The `@lid` limitation is now stated plainly in the README instead of living only
in code comments. A contact arriving privacy-id-first, or one whose mapping is
lost, can appear as a second Chatwoot contact. It costs no messages and cannot
affect a thread already mapped, but stable should mean "we know where the edges
are", not "there are none".
@rmyndharis
rmyndharis merged commit 088b507 into main Jul 22, 2026
1 check passed
@rmyndharis
rmyndharis deleted the fix/chatwoot-idless-own-send branch July 22, 2026 08:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant