Skip to content

feat(chatwoot-adapter): retry failed inbound relays instead of dropping them (#609)#30

Merged
rmyndharis merged 1 commit into
mainfrom
feat/chatwoot-inbound-retry
Jul 3, 2026
Merged

feat(chatwoot-adapter): retry failed inbound relays instead of dropping them (#609)#30
rmyndharis merged 1 commit into
mainfrom
feat/chatwoot-inbound-retry

Conversation

@rmyndharis

Copy link
Copy Markdown
Owner

Summary

Makes inbound WhatsApp → Chatwoot relay resilient to transient Chatwoot outages. A failed relay was previously logged and dropped (at-most-once); it is now retried from a durable, storage-backed queue until it succeeds or is dead-lettered (#609).

Problem

When the Chatwoot API is briefly unreachable — a deploy, a restart, a network blip — inbound customer messages were silently lost: the failed POST was logged and the message discarded.

Changes

  • At-least-once inbound. On a relay failure the message is enqueued to a durable ctx.storage queue instead of being dropped, and re-posted on a 30-second timer until it succeeds. A message that keeps failing is dead-lettered after 5 attempts.
  • Health check. The plugin reports the pending retry backlog and any dead-lettered messages, and reads unhealthy when the queue saturates — so ongoing loss is visible in the dashboard rather than hidden.
  • Bounded and safe. The queue is capped (the oldest entry is dropped on overflow); an oversized media blob is stripped before persisting so a large value can't be rejected and lose the message (it retries as a placeholder); the drain is single-flight and is skipped while the config is invalid; the timer is .unref()'d and cleared on disable/unload.

markSeen stays before the relay — it dedups WhatsApp re-deliveries and lets history backfill skip the live message; the pending-queue entry is the "not yet relayed" signal, independent of that marker.

Trade-off

At-least-once means a message that actually reached Chatwoot but whose response was lost may, on rare occasions, re-post as a duplicate. Posts carry source_id (the WhatsApp message id) as a dedup handle.

Compatibility

Version 0.5.0. No core change (minOpenWAVersion stays 0.8.7). Existing behavior is unchanged for outbound replies, own-send relay, and handover.

…ng them (#609)

Inbound relay was at-most-once: a failed Chatwoot POST was logged and the
message dropped. When Chatwoot is transiently unreachable this silently loses
customer messages. Make inbound at-least-once via a durable, storage-backed
retry queue drained on a timer.

- inbound.ts: on a relay failure, enqueue the message (media over a size cap is
  stripped so an oversized value can't be rejected and lose the message) rather
  than only logging. markSeen stays before the relay (dedups WA re-deliveries +
  makes backfill skip the live message); the pending-queue entry is the
  not-yet-relayed signal.
- retry.ts: drainRetries re-posts each queued message under its per-chat lock,
  streaming one entry at a time so a large media backlog is never fully resident
  in memory; a message that keeps failing is dead-lettered after 5 attempts.
- mapping-store.ts: retry-queue persistence (individual retry:* keys, one file
  each; count/list-keys without loading blobs; overflow drops the oldest,
  set-before-delete).
- index.ts: 30s .unref() drain timer with a single-flight guard, cleared on
  disable/unload and skipped while config is invalid; healthCheck surfaces the
  pending backlog + dead-letters and reads unhealthy when the queue saturates.

At-least-once means a message that reached Chatwoot but whose response was lost
may rarely re-post as a duplicate; posts carry source_id as a dedup handle.

chatwoot-adapter 0.5.0; no core change (minOpenWAVersion stays 0.8.7).
@rmyndharis rmyndharis force-pushed the feat/chatwoot-inbound-retry branch from 4094673 to 9f3a31c Compare July 3, 2026 13:57
@rmyndharis rmyndharis merged commit 9290012 into main Jul 3, 2026
1 check passed
@rmyndharis rmyndharis deleted the feat/chatwoot-inbound-retry branch July 3, 2026 14:03
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