Overview
This guide covers sending messages, including text, reactions, channel comments, quotes, album messages (grouped media), sticker packs, and message editing operations using the whatsapp-rust library.Sending text messages
Simple text message
Use theconversation field for plain text messages:
send_message returns a SendResult containing the message_id and recipient to JID. Use result.message_key() to get a wa::MessageKey for follow-up operations like album child linking.
Extended text message
For messages with formatting, links, or context (replies/quotes):Quoted Replies
Replying to a message
Usebuild_quote_context to create a basic reply:
Cross-platform quoted replies with remoteJid
For quoted replies that display correctly on all platforms (including iOS), usebuild_quote_context_with_info. It matches WhatsApp Web’s behavior: it sets participant correctly (channel JID for newsletters, sender for everything else) and emits remote_jid only for cross-chat quotes — when the quoted message lives in a different chat than the one you’re sending to.
The function takes both the quoted message’s chat (quoted_chat_jid) and the chat you’re sending into (target_chat_jid). For an in-place reply these are the same JID, and remote_jid is omitted; for a cross-chat quote (for example, quoting a status update into a DM) they differ, and remote_jid is set to the quoted chat:
build_quote_context_with_info function handles two important details:
remote_jidis set only whenquoted_chat_jidandtarget_chat_jidrefer to different chats (cross-chat quote). For same-chat replies it is omitted, matching WhatsApp Web.participantis set to the sender JID for normal chats, or the newsletter JID for newsletter quotes.
Pass the same JID for both
quoted_chat_jid and target_chat_jid when replying in-place. Use different JIDs only when forwarding a quote across chats (for example, quoting a status into a DM).Setting context on media messages
You can add quote context to any message type usingset_context_info:
Reactions
Sending a reaction
Useclient.send_reaction() to react to a DM, group, or status@broadcast message. The helper builds the ReactionMessage payload, stamps sender_timestamp_ms, and routes the stanza through the standard send path.
status@broadcast, target_key.participant must point to the original sender so the receipt can be attributed. In DMs, leave participant as None.
If you’re already inside an event handler, MessageContext::react fills in chat, target_key, and participant from the incoming message automatically:
Reactions in Community Announcement Groups
Community Announcement Groups (CAGs) — the default announcement subgroup of a community — require encrypted reactions.send_reaction handles this transparently: it detects CAG chats automatically and sends the reaction as an encrypted enc_reaction_message envelope instead of a plaintext stanza.
No change to your call is needed. The only requirement is that the target post’s messageSecret was captured when the post was received. If it was not captured (for example, msg_secret_policy is disabled without a resolver, or the post arrived before the current session), the call returns an error rather than emitting a plaintext reaction the channel would reject.
Removing a reaction
Pass an emptyemoji to revoke a previous reaction (matches WhatsApp Web’s empty-text-as-revoke semantics):
Newsletter (channel) reactions use a different plaintext stanza format. Use
client.newsletter().send_reaction() for newsletters instead.Channel Comments
Channel comments are encrypted threaded replies under a Community Announcement Group (CAG) post. Useclient.comments() to send them:
parent_key.participant field must identify the post author so receivers can derive the HKDF decryption key from the envelope. When from_me is true and participant is absent the library resolves the author to your own identity.
Incoming encrypted comments are decrypted transparently. The comment body is dispatched as Event::Message and the parent post key is available on MessageInfo::comment_target:
Editing messages
Useclient.edit_message to replace the content of a message you previously sent. Pass the chat JID, the original message ID, and the new content as a plain wa::Message — the client builds the correct wire envelope, resolves the participant JID (LID or PN) for groups, and sends the edit with a fresh stanza ID so the server does not deduplicate it against the original.
Message editing only works for text messages (
conversation or extended_text_message) sent by you within the last 15 minutes.Deleting messages (revoke)
Delete your own message
Admin delete (group only)
Group admins can delete messages from other participants:Sending to newsletters
Newsletter messages are sent through the sameclient.send_message() method. The library automatically detects newsletter JIDs and sends messages as plaintext (no Signal encryption):
type (text, media, reaction, poll), mediatype attributes, and <meta> nodes (for polls, events, etc.) are inferred automatically from the message content. See the Newsletters guide for more details.
Newsletter reactions use a different protocol format. Use
client.newsletter().send_reaction() for reactions instead of send_message().Album messages
Album messages let you send multiple images and/or videos as a grouped media album — the collapsed album bubble that WhatsApp displays when someone sends several photos at once. An album consists of:- A parent
AlbumMessagedeclaring the expected image and video counts - Multiple child messages (individual media messages) wrapped with
wrap_as_album_childand linked to the parent
Sending an album
How it works
Thewrap_as_album_child function (from whatsapp_rust::proto_helpers) takes a media wa::Message and a parent wa::MessageKey, then:
- Wraps the inner message in an
associated_child_message(FutureProofMessageenvelope) - Attaches a
MessageAssociationwith typeMediaAlbumpointing to the parent - Lifts any existing
message_context_infofrom the inner message to the outer wrapper
The parent
AlbumMessage declares the total expected counts so WhatsApp clients know how many media items to group together. Each child is sent as a separate message linked back to the parent via MessageAssociation.Mixed albums (images and videos)
You can mix images and videos in the same album:Sticker packs
Sticker packs let you send a collection of stickers as a single message — the inline sticker pack bubble that WhatsApp displays with a tray icon, pack name, and publisher info. A sticker pack requires:- Sticker images — 512x512 WebP files
- Cover image — WebP file used as the tray icon
- Thumbnail — JPEG uploaded separately with the same
media_keyas the ZIP - Metadata — pack ID, name, and publisher
Sending a sticker pack
How it works
The sticker pack flow uses two helper functions fromwacore::sticker_pack:
create_sticker_pack_zip— bundles stickers and a cover image into a ZIP file. Filenames usebase64url(sha256).webp, and identical stickers are deduplicated. Returns aStickerPackZipResultcontaining the ZIP bytes and proto metadata.build_sticker_pack_message— constructs awa::Messagewith aStickerPackMessagefrom the ZIP result and upload responses.
MediaType::StickerPackThumbnail and the same media_key as the ZIP upload. The UploadResponse implements Into<MediaUploadInfo> for convenience.
Sticker format requirements
| Field | Requirement |
|---|---|
| Sticker images | 512x512 WebP |
| Cover image | WebP, stored in ZIP as {pack_id}.webp |
| Thumbnail | JPEG, uploaded separately with same media_key |
| Pack ID | Non-empty, max 128 bytes, no path separators or control chars |
| Sticker count | 1 to 60 per pack |
Sticker metadata
Each sticker supports optional metadata:Animated stickers are automatically detected from the WebP data. The
is_animated field on each sticker proto entry is set based on whether the WebP file contains animation frames. You can also use whatsapp_rust::webp::is_animated() directly to check WebP files before processing.Forwarding messages
Useforward_message to forward any received message to a chat. It produces the same on-wire result as tapping Forward in the official clients:
- Sets
context_info.is_forwarded = trueso recipients see the Forwarded label. - Bumps the forwarding score. Once the score reaches 5, it jumps to the
127sentinel that clients render as Forwarded many times. - Strips the reply/quote chain and mentions from the source.
- Drops the source
message_secretso the send path mints a fresh one. - Unwraps ephemeral and view-once wrappers before sending the inner content.
- Promotes a bare
conversationtoextended_text_messageso the forward marker can attach.
Manually forwarding media
If you need to customize fields (for example, change a caption) before forwarding, you can still build thewa::Message yourself and pass it to send_message. Reusing the original CDN fields keeps the send instant:
forward_message whenever you want the recipient to see the standard forward indicator. See Media handling — CDN reuse for details on supported media types and when to fall back to download + re-upload.
Ephemeral (disappearing) messages
WhatsApp supports disappearing messages that automatically delete after a set duration. When a chat has disappearing messages enabled, you should set the ephemeral expiration on outgoing messages so recipients see the correct countdown timer.Sending a disappearing message
Usesend_message_with_options with ephemeral_expiration set to the chat’s timer value:
contextInfo.expiration on the protobuf message, which tells WhatsApp clients to display the disappearing countdown.
Common timer values
| Duration | Value (seconds) |
|---|---|
| 24 hours | 86400 |
| 7 days | 604800 |
| 90 days | 7776000 |
| Disabled | 0 |
Getting the chat’s ephemeral timer
For groups, read the timer from group metadata:MessageInfo:
Configuring disappearing messages
Per-group: Useset_ephemeral to enable or disable disappearing messages on a group:
set_default_disappearing_mode to set the default for all new 1-on-1 chats:
The account-level default only applies to new chats. Existing chats keep their current setting. To change a specific group’s timer, use
set_ephemeral.Listening for timer changes
When a contact changes their default disappearing messages setting, you receive aDisappearingModeChanged event:
GroupUpdate event with a GroupNotificationAction::Ephemeral action containing the new expiration value.
See Events reference for details.
Creating groups with disappearing messages
You can enable disappearing messages at group creation time:SendOptions type.
Send Options
Specifying a custom message ID
You can override the auto-generated message ID by settingmessage_id on SendOptions. This is useful for resending a failed message with the same ID or ensuring idempotency:
Overriding the stanza type
The library infers the<message type="..."> attribute from the protobuf content of every send. If you’re sending a message variant the classifier can’t recognize, set stanza_type_override to force a specific wire type:
None for every supported message type — the classifier already picks the right value, and the override is not preserved across the retry path. See Stanza types for the available variants.
Adding extra stanza nodes
For advanced use cases, you can include custom XML nodes:Message preparation helpers
Preparing messages for quoting
Theprepare_for_quote method strips nested context info:
- Nested mentions are stripped
- Quote chains are broken (except for bot messages)
- Content fields (text, caption, media) are preserved
Preparing messages for forwarding
prepare_for_forward is the lower-level helper that powers forward_message. It returns a forward-ready wa::Message (forward marker set, score bumped, quote chain stripped, source message_secret dropped) without sending anything. Reach for it when you need to attach extra fields — for example, a custom caption or stanza nodes — before calling send_message_with_options:
client.forward_message(to, &message) — it unwraps wrapper bodies (ephemeral, view-once) and sends in one call.
See WAProto API reference for message type details.
Error Handling
Best Practices
conversationextended_text_messageextended_text_message with context_infoalbum_message parent + wrap_as_album_child for grouped mediacreate_sticker_pack_zip + build_sticker_pack_message with two uploads (ZIP + thumbnail)send_message_with_options with ephemeral_expiration matching the chat’s timersend_reaction — encryption is applied automatically for CAG chatsclient.comments().send_text() or send_message()// Store the result for later operations
let result = client.send_message(to, message).await?;
// Use the message ID for reactions, edits, or deletes
client.revoke_message(to, &result.message_id, RevokeType::Sender).await?;
// Use message_key() for album child linking or pinning
let key = result.message_key();
prepare_for_quote() to avoid nested quote chainsbuild_quote_context_with_info instead of build_quote_context for cross-platform compatibility — it correctly resolves the participant field for newsletters and emits remote_jid only for cross-chat quotes (matching WhatsApp Web)Next Steps
- Receiving Messages - Handle incoming messages and events
- Media Handling - Upload and download media
- Polls - Create polls and process votes
- Group Management - Work with group chats
- Community management - CAG reactions and channel comments