Skip to content

docs: Event::Messages batching and InboundDurabilityHook::on_messages (whatsapp-rust#961) - #370

Merged
jlucaso1 merged 7 commits into
mainfrom
claude/nifty-bohr-3opgcu
Jul 3, 2026
Merged

docs: Event::Messages batching and InboundDurabilityHook::on_messages (whatsapp-rust#961)#370
jlucaso1 merged 7 commits into
mainfrom
claude/nifty-bohr-3opgcu

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

Updates the docs for oxidezap/whatsapp-rust#961, which batches the inbound commit pipeline during the offline drain. This is a breaking change to the public event/hook API:

Before After
Event::Message(Arc<wa::Message>, Arc<MessageInfo>) Event::Messages(MessageBatch)
EventKind::Message EventKind::Messages
Event::as_message() / Event::message_text() Event::as_messages() / Event::messages()
InboundDurabilityHook::on_message(info, message) InboundDurabilityHook::on_messages(batch: &[InboundMessage])
MessageContext::from_event(&Event) -> Option<Self> MessageContext::from_inbound(&InboundMessage) -> Self (infallible)

New types InboundMessage, MessageBatch, BatchOrigin are introduced and re-exported from the prelude. ProtocolStore gains optional store_pending_inbound_batch / delete_pending_inbound_batch methods (default to looping the existing single-row methods, so third-party backends keep working).

Live traffic still dispatches a batch of one (no latency change); during the offline drain, messages accumulate into a batch (200 messages / 4 MiB / 3s timeout, or forced flush at drain end) before committing, mirroring WhatsApp Web's MessageProcessorCache.

Changes

  • advanced/inbound-durability.mdx — rewritten for batch hook semantics: new "Batching" section (triggers, commit order, raceless drain→live transition), updated trait signature, idempotency and redelivery sections, backend requirements (batch methods), and the full worked example from examples/durability_hook.rs.
  • concepts/events.mdxMessageMessages event section rewritten with InboundMessage/MessageBatch/BatchOrigin definitions; every Event::Message match/example updated to iterate a batch.
  • guides/receiving-messages.mdx — event enum listing, all message-type examples, PDO recovery section, durability hook example, and best-practices examples updated for the batch shape.
  • api/bot.mdxMessageContext section: from_event replaced with from_inbound, plus all on_event/ChannelEventHandler/complete examples.
  • guides/custom-backends.mdx — documents the new store_pending_inbound_batch/delete_pending_inbound_batch methods and their default fallback.
  • api/client.mdx, api/polls.mdx, api/signal.mdx, concepts/architecture.mdx, guides/communities.mdx, guides/media-handling.mdx, guides/sending-messages.mdx — remaining Event::Message references and anchors found via a repo-wide sweep.

Changelog entries are intentionally not touched (human-authored only).

Test plan

  • Mintlify preview renders without broken code fences or anchors
  • Spot-check /concepts/events#messages and /advanced/inbound-durability#batching anchors resolve correctly

Generated by Claude Code


Summary by cubic

Docs adopt batched inbound commits and event batching. Event::Message is replaced by Event::Messages(MessageBatch) and InboundDurabilityHook::on_message by on_messages, with clear batching semantics, backend guidance, and updated examples.

  • New Features

    • Documents offline-drain batching with commit triggers (200 messages, 4 MiB, 3s; forced flush on drain end). Live traffic stays batch-of-one.
    • Introduces InboundMessage, MessageBatch, BatchOrigin, plus Event::as_messages()/Event::messages(); examples iterate a batch. Adds missing InboundMessage imports in self-contained snippets (including the first EventHandler example).
    • Describes ProtocolStore batch methods store_pending_inbound_batch/delete_pending_inbound_batch (defaults loop single-row methods); SqliteStore commits whole batches atomically. Corrects examples to key pending-inbound on (chat, sender, id) only — no device_id parameter; backends scope device/account internally.
    • Clarifies on_messages hook semantics: commit the whole batch atomically or use idempotent per-item upserts — the SDK redelivers the entire batch on error. Notes that Event::Messages is at-least-once when a hook is registered. Updates English and Portuguese quickstarts and fixes loop indentation.
  • Migration

    • Handlers: Event::Message(msg, info)Event::Messages(batch); loop for InboundMessage { message, info } in batch.iter(). Also switch EventKind::MessageEventKind::Messages.
    • Durability hooks: on_message(info, message)on_messages(batch: &[InboundMessage]); prefer single-transaction commits, or idempotent per-item upserts.
    • Helpers: MessageContext::from_eventMessageContext::from_inbound; use event.as_messages()/event.messages() when scanning mixed streams.
    • Backends: optionally override the new batch store/delete methods for single-transaction commits; existing backends keep working via defaults.

Written for commit f32d6e6. Summary will update on new commits.

… (whatsapp-rust#961)

whatsapp-rust#961 batches the inbound commit pipeline during the offline
drain: Event::Message(msg, info) is now Event::Messages(MessageBatch),
InboundDurabilityHook::on_message became on_messages(batch), and
MessageContext::from_event was replaced by the infallible from_inbound.
Updates every doc page referencing the old single-message shapes, adds
batching semantics to the inbound durability guide, and documents the
new store_pending_inbound_batch/delete_pending_inbound_batch methods.
@mintlify

mintlify Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
whatsapp-rust 🟢 Ready View Preview Jul 3, 2026, 4:57 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@jlucaso1, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 34 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 15439819-5916-40c0-81d3-7eff00a85ec7

📥 Commits

Reviewing files that changed from the base of the PR and between fe4a53d and f32d6e6.

📒 Files selected for processing (13)
  • advanced/inbound-durability.mdx
  • api/bot.mdx
  • api/client.mdx
  • api/polls.mdx
  • api/signal.mdx
  • concepts/architecture.mdx
  • concepts/events.mdx
  • guides/communities.mdx
  • guides/custom-backends.mdx
  • guides/media-handling.mdx
  • guides/receiving-messages.mdx
  • guides/sending-messages.mdx
  • pt/quickstart.mdx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 67477fa6a1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread guides/custom-backends.mdx Outdated
The ProtocolStore trait's store_pending_inbound/get_pending_inbound/
delete_pending_inbound/delete_expired_pending_inbound (and their batch
counterparts) key on (chat, sender, id) only — verified against
wacore/src/store/traits.rs and the bundled SqliteStore, which scopes
by its own self.device_id field rather than accepting it as an
argument. The previous examples showed a device_id parameter that
doesn't exist on the trait, which a batch-delete example added for
whatsapp-rust#961 then also omitted, producing a same-page
inconsistency flagged in review.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found and verified against the latest diff

Confidence score: 3/5

  • In api/client.mdx, the example for loop references InboundMessage without importing it, so the sample won’t compile and users copying it will hit immediate build errors—add InboundMessage to the import list before merging.

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread api/client.mdx
cubic flagged api/client.mdx's example destructuring InboundMessage
without importing it. Swept the other complete/self-contained examples
(full Bot::builder()...build().await? snippets and the #[tokio::main]
program) for the same gap — bare match-arm fragments that never
imported Event either are left as-is, matching existing doc convention.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 4 files (changes from recent commits).

Auto-approved: Documentation-only changes updating examples and descriptions to reflect a batch API. No code, database, or business logic changes.

Re-trigger cubic

@greptile-apps

greptile-apps Bot commented Jul 3, 2026

Copy link
Copy Markdown

Greptile Summary

This PR updates all documentation for the batched inbound message delivery API introduced in whatsapp-rust#961. Event::Message(Arc<wa::Message>, Arc<MessageInfo>) is replaced throughout with Event::Messages(MessageBatch), InboundDurabilityHook::on_message becomes on_messages(batch: &[InboundMessage]), and MessageContext::from_event becomes the infallible MessageContext::from_inbound.

  • 13 files updated: advanced/inbound-durability.mdx is rewritten for batch semantics with a new "Batching" section; concepts/events.mdx documents InboundMessage, MessageBatch, and BatchOrigin; all remaining Event::Message references and anchors across guides and API pages are swept.
  • New types and helpers (InboundMessage, MessageBatch, BatchOrigin, Event::as_messages(), Event::messages()) are fully documented; ProtocolStore batch methods and their fallback defaults are described in guides/custom-backends.mdx; the Portuguese quickstart is updated to the batch API.

Confidence Score: 5/5

Documentation-only PR with no runtime code changes; all 13 files correctly migrate Event::Message to Event::Messages throughout.

Every changed file consistently adopts the new batch API — imports updated, patterns converted, anchors fixed, and the Portuguese quickstart aligned. The Opting In placeholder passes only the message ID where the docs elsewhere recommend the full (chat, sender, id) triplet, and the full example uses a different (but valid) module alias for InboundMessage — both minor clarity issues. No broken anchors, missing imports in self-contained examples, or stale API references were found.

advanced/inbound-durability.mdx — the Opting In placeholder key and the full-example type annotation have minor inconsistencies worth a quick look.

Important Files Changed

Filename Overview
advanced/inbound-durability.mdx Rewritten for batch semantics: new Batching section, updated trait signature to on_messages, idempotency and redelivery sections, backend requirements, and new full example. Minor inconsistency: full example uses whatsapp_rust::types::events::InboundMessage while the trait definition section uses wacore::types::events::InboundMessage.
concepts/events.mdx Message → Messages section rewritten; InboundMessage/MessageBatch/BatchOrigin types documented; imports added to EventHandler implementation example; EventKind::Messages updated throughout.
guides/receiving-messages.mdx All Event::Message patterns updated to iterate over MessageBatch; durability hook example updated to on_messages; PDO recovery text updated to Event::Messages.
api/bot.mdx MessageContext section updated: from_event removed, from_inbound documented as infallible; all on_event examples use Event::Messages and batch iteration.
guides/custom-backends.mdx New store_pending_inbound_batch and delete_pending_inbound_batch batch methods documented with override examples and atomicity notes.
guides/media-handling.mdx Updated to Event::Messages batch pattern with correct InboundMessage import and properly indented for-loop body.
pt/quickstart.mdx Correctly updated to Event::Messages with InboundMessage import and batch iteration in both the main example and the step-by-step section.
api/client.mdx register_handler example updated: InboundMessage added to imports and Event::Message → Event::Messages with for-loop.
concepts/architecture.mdx Step 3/4 in handle_message pseudocode updated for batch commit; note on drain→live transition tail flush added; Event::Messages reference updated.
api/polls.mdx Single link and anchor updated: Event::Message → Event::Messages and #message → #messages.
api/signal.mdx Bot message decryption description updated from Event::Message to Event::Messages with correct anchor.
guides/communities.mdx Reaction and channel comment snippets updated to Event::Messages batch iteration.
guides/sending-messages.mdx MessageContext::from_event replaced with from_inbound via event.messages() iterator; comment-receiving snippet updated to batch pattern.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant WA as WhatsApp Server
    participant Client as wacore Client
    participant Batcher as Batch Accumulator
    participant Hook as InboundDurabilityHook
    participant Store as ProtocolStore
    participant Handler as EventHandler

    Note over WA,Handler: Live traffic (batch of 1)
    WA->>Client: Encrypted stanza
    Client->>Store: store_pending_inbound
    Client->>Client: Signal ratchet flush
    Client->>Hook: on_messages([InboundMessage])
    Hook-->>Client: Ok(())
    Client->>Store: delete_pending_inbound
    Client->>WA: ack
    Client->>Handler: "Event::Messages(batch, origin=Live)"

    Note over WA,Handler: Offline drain (accumulated batch)
    loop until trigger (200 msgs / 4MiB / 3s / drain end)
        WA->>Client: Encrypted stanza
        Client->>Batcher: accumulate InboundMessage
    end
    Batcher->>Store: store_pending_inbound_batch
    Batcher->>Client: Signal ratchet flush (all)
    Batcher->>Hook: on_messages([InboundMessage...])
    Hook-->>Batcher: Ok(())
    Batcher->>Store: delete_pending_inbound_batch
    Batcher->>WA: acks (all)
    Batcher->>Handler: "Event::Messages(batch, origin=OfflineDrain)"
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant WA as WhatsApp Server
    participant Client as wacore Client
    participant Batcher as Batch Accumulator
    participant Hook as InboundDurabilityHook
    participant Store as ProtocolStore
    participant Handler as EventHandler

    Note over WA,Handler: Live traffic (batch of 1)
    WA->>Client: Encrypted stanza
    Client->>Store: store_pending_inbound
    Client->>Client: Signal ratchet flush
    Client->>Hook: on_messages([InboundMessage])
    Hook-->>Client: Ok(())
    Client->>Store: delete_pending_inbound
    Client->>WA: ack
    Client->>Handler: "Event::Messages(batch, origin=Live)"

    Note over WA,Handler: Offline drain (accumulated batch)
    loop until trigger (200 msgs / 4MiB / 3s / drain end)
        WA->>Client: Encrypted stanza
        Client->>Batcher: accumulate InboundMessage
    end
    Batcher->>Store: store_pending_inbound_batch
    Batcher->>Client: Signal ratchet flush (all)
    Batcher->>Hook: on_messages([InboundMessage...])
    Hook-->>Batcher: Ok(())
    Batcher->>Store: delete_pending_inbound_batch
    Batcher->>WA: acks (all)
    Batcher->>Handler: "Event::Messages(batch, origin=OfflineDrain)"
Loading

Reviews (4): Last reviewed commit: "docs(events): import InboundMessage in t..." | Re-trigger Greptile

Comment thread guides/media-handling.mdx
Comment thread concepts/events.mdx

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0d564eaf82

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread advanced/inbound-durability.mdx
claude added 2 commits July 3, 2026 17:15
- guides/media-handling.mdx: add missing InboundMessage import, reindent
  the for-loop body one level deeper (was flush with the for statement)
- guides/receiving-messages.mdx: same reindent for the text-message and
  DeviceSentMessage examples
- concepts/events.mdx: reindent the Message Events walkthrough example;
  note that the event-filtering example's two if-blocks are independent
  filters, not mutually exclusive match arms like the original
The "Opting In" on_messages example claimed to commit the batch "in
one transaction" but the per-item await loop commits each row
independently — a later item's Err leaves earlier items durably
committed while the SDK still redelivers the whole batch. Documents
that my_db_insert must be an idempotent upsert so that redelivery
replay of an already-committed item is a no-op, matching the
Idempotency Requirement section below it.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fc1e17c7c7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +598 to +600
for item in batch {
my_db_insert(&item.info.id, &item.message).await?;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Make the hook example batch-idempotent

This guide still shows committing each item independently with a plain my_db_insert; during an offline-drain batch, if a later insert returns Err, the SDK suppresses every ack and redelivers the whole batch, so any earlier successful inserts will be applied again unless this function is explicitly an idempotent upsert or the loop is wrapped in one transaction. The advanced durability page now calls this out, but this duplicate example can still teach users to write non-idempotent batch hooks.

Useful? React with 👍 / 👎.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 4 files (changes from recent commits).

Auto-approved: Updates documentation to reflect batched inbound event and hook API—no source code changes, low risk.

Re-trigger cubic

…cy note

Greptile flagged pt/quickstart.mdx as missed by the whatsapp-rust#961
sweep: it still had four Event::Message(msg, info) match arms and one
MessageContext::from_event call that no longer exist. Mechanically
updated all five to the batch API/from_inbound, matching the pattern
used across the English docs; left the page's existing on_event-based
structure alone rather than restructuring it to match the newer
on_message-centric English quickstart (a separate, larger sync).

Codex also found the receiving-messages.mdx hook example has the same
non-atomic per-item commit loop already called out in
advanced/inbound-durability.mdx; added the same idempotency note here.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 2 files (changes from recent commits).

Auto-approved: This PR updates documentation to reflect API changes (Event::Message -> Event::Messages, batching semantics, new types). No code or infrastructure changes; purely docs.

Re-trigger cubic

Greptile caught a real distinction: the old Event::Message(msg, info)
tuple pattern needed no import, but destructuring InboundMessage as a
struct pattern does. Every other converted example either already had
the import or was a bare match-arm fragment; this was the one
complete, self-contained snippet that needed it.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 1 file (changes from recent commits).

Auto-approved: Documentation-only update renaming Event::Message to Event::Messages and on_message to on_messages with batching. No source code, config, or infrastructure changes — extremely low risk.

Re-trigger cubic

@jlucaso1
jlucaso1 merged commit 2fd77e0 into main Jul 3, 2026
4 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f32d6e604d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


These triggers are internal constants, not currently exposed as configuration. The drain→live transition is raceless: the tail batch of the drain always commits before any live-mode message is processed, so a consumer never observes drain and live messages out of order across the boundary.

Within a batch, the commit order is: durable buffer write (one transaction) → Signal-cache flush → your hook → buffer clear → acks → `Event::Messages` dispatch. A failure at any step leaves the **entire batch** unacked, and the server redelivers all of it — so your hook must commit a batch all-or-nothing.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Don't promise transactional buffering for fallback batches

When a custom backend implements only the single-row pending-inbound methods, the batch methods below default to looping those calls, so the durable buffer write/delete is not one transaction unless the backend overrides the batch methods. In that scenario a mid-batch store or cleanup error can leave only part of the batch buffered/cleared while the SDK suppresses the whole batch's acks, so this sentence overstates the guarantee and contradicts the later guidance that overriding is optional; qualify the transaction claim to SqliteStore/overridden batch methods or document the fallback semantics here.

Useful? React with 👍 / 👎.

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.

2 participants