Skip to main content
Examples on this page target source snapshot 7e5c198e, not a crates.io release. Use the current API guide to select the matching dependency.
The Newsletter feature provides methods for managing WhatsApp newsletter channels, including creation, subscription management, admin operations, reactions, and live updates. Newsletter operations use MEX (GraphQL) for metadata/management and IQ stanzas for message operations. Newsletter message sending is handled by the unified client.send_message() method — see the Send API. Reactions remain on the Newsletter struct because they use a different stanza format.
Newsletter messages are plaintext — they are not encrypted with the Signal protocol.

Access

Access newsletter operations through the client:

Methods

list_subscribed

List all newsletters the user is subscribed to.
Returns:
  • Vec<NewsletterMetadata> — List of subscribed newsletters
If the server returns an entry that is null or has no id, the client skips it and logs a warning. The rest of the list is still returned. An entry whose id doesn’t parse as a JID still fails the whole call with NewsletterError::InvalidRequest. Example:

get_metadata

Fetch metadata for a newsletter by its JID.
Parameters:
  • jid — Newsletter JID (server must be newsletter)
Returns:
  • NewsletterMetadata — Full newsletter metadata
Example:
Errors: Returns NewsletterError::NotFound(jid) when no channel exists behind the JID. A malformed server response returns NewsletterError::InvalidRequest.

get_metadata_by_invite

Fetch metadata for a newsletter by its invite code.
Parameters:
  • invite_code — Newsletter invite code string
Returns:
  • NewsletterMetadata — Full newsletter metadata
Example:

create

Create a new newsletter.
Parameters:
  • name — Newsletter name
  • description — Optional description
Returns:
  • NewsletterMetadata — Metadata of the newly created newsletter
Example:

join

Join (subscribe to) a newsletter.
Parameters:
  • jid — Newsletter JID to join
Returns:
  • NewsletterMetadata — Metadata with the viewer’s role set to Subscriber
Example:

leave

Leave (unsubscribe from) a newsletter.
Parameters:
  • jid — Newsletter JID to leave
Example:

update

Update a newsletter’s name and/or description.
Parameters:
  • jid — Newsletter JID
  • name — New name, or None to keep the current name
  • description — New description, or None to keep the current description
Returns:
  • NewsletterMetadata — Updated metadata
Example:

set_picture

Replace a newsletter’s picture. Carried by the same mutation as update — WhatsApp Web edits the picture as base64-encoded JPEG bytes — so the response is the newsletter’s refreshed metadata.
Parameters:
  • jid — Newsletter JID
  • jpeg — JPEG-encoded image bytes
Returns:
  • NewsletterMetadata — Updated metadata
Example:

remove_picture

Remove a newsletter’s picture.
Parameters:
  • jid — Newsletter JID
Returns:
  • NewsletterMetadata — Updated metadata
Example:

delete

Delete a newsletter. Owner-only.
Parameters:
  • jid — Newsletter JID
Example:

change_owner

Transfer a newsletter’s ownership to another user. Owner-only.
Parameters:
  • jid — Newsletter JID
  • user — The new owner. May be a LID or a phone-number JID — a phone-number JID is resolved to its LID before the request. If no LID is known for the target, the call fails with NewsletterError::InvalidRequest rather than sending an address the server can’t route.
Example:

demote_admin

Demote an admin of a newsletter back to subscriber. Owner-only.
Parameters:
  • jid — Newsletter JID
  • user — The admin to demote. Same LID resolution as change_owner.
Example:

get_admin_info

Fetch a newsletter’s admin-side information, including its admin count.
Parameters:
  • jid — Newsletter JID
Returns:
  • NewsletterAdminInfo — Admin count, the viewer’s own admin profile, and the admin-profiles setting
The admin count has no query of its own — it rides along with the admin profile and the admin-profiles setting in this one response. The server only answers it for admins and owners, so admin_count is None for everyone else.
Example:

get_followers

List up to count of a newsletter’s followers (subscribers).
Parameters:
  • jid — Newsletter JID
  • count — Maximum number of followers to return
This operation takes no cursor — WhatsApp Web asks for a single page clamped to its subscriber-list limit, so count is the whole request. There is no built-in pagination.
Returns:
  • Vec<NewsletterFollower> — One page of followers
Example:

set_follower_mute

Mute or unmute a newsletter’s follower-activity notifications (WhatsApp Web’s MUTE_FOLLOWER_ACTIVITY). Sent via MEX as a user-setting update. Only owners and admins receive these notifications, so this setting is not surfaced to plain followers. The result is reflected in NewsletterMetadata::follower_activity_muted.
Parameters:
  • jid — Newsletter JID
  • muted — true silences notifications, false re-enables them
Example:

set_admin_mute

Mute or unmute a newsletter’s channel updates (WhatsApp Web’s MUTE_ADMIN_ACTIVITY). This is the “Mute” toggle a follower sees on the channel in WhatsApp, and the result is reflected in NewsletterMetadata::muted.
Parameters:
  • jid — Newsletter JID
  • muted — true silences admin-activity notifications, false re-enables them
Example:
The mute state is sent as ON/OFF; the mute expiration is local database state and is never put on the wire, matching WhatsApp Web’s WAWebNewsletterUpdateUserSettingJob.

Sending messages

Newsletter message sending is handled by the unified client.send_message() method. See the Send API reference for full details.
The library detects newsletter recipients automatically and sends messages as plaintext (no Signal encryption), with the correct type and mediatype stanza attributes inferred from the message content. Stanza-level <meta> nodes (for polls, events, etc.) are also included automatically, matching WhatsApp Web behavior.
Newsletter::send_message() was removed. Use client.send_message() instead — it accepts newsletter, group, and direct message JIDs.

send_reaction

Use NewsletterMessageRef to preserve separate client and server content IDs. A fetched NewsletterMessage::message_ref(chat) and an inbound message’s newsletter_ref() construct the relevant association without cloning the message body.
A reaction requires a server content ID and returns a new StanzaId for ACK correlation. It is not a delivery guarantee. A missing server ID is rejected; it is never synthesized from a client ID.

send_poll_vote

send_poll_vote(&NewsletterMessageRef, &[[u8; 32]]) replaces the full selection. Hash option names exactly as the original poll carries them, preserving variation selectors and ZWJ sequences. An empty selection removes your vote; duplicate hashes and oversized selections are rejected.

edit_message

edit_message(&NewsletterMessageRef, replacement) uses the client content MessageId, not its server ID. This is the channel plaintext path; root chat edits take EditRequest. Missing client IDs are rejected rather than derived from another ID domain.

revoke_message

revoke_message(&NewsletterMessageRef) likewise requires the client content ID. Explicit send_reaction_raw, send_poll_vote_raw, edit_message_raw and revoke_message_raw remain for external byte/ID integrations. Consult the pinned source for exact signatures and ownership checks. A reference’s optional ownership declaration does not grant server permission. Channel actions and history message references

get_messages

Fetch message history from a newsletter.
Parameters:
  • jid — Newsletter JID
  • count — Maximum number of messages to return
  • before — If set, return messages before this server_id (for pagination)
Returns:
  • Vec<NewsletterMessage> — List of newsletter messages
Example:

get_my_addons

Read your own reaction and poll vote on a newsletter’s recent messages.
Parameters:
  • jid — Newsletter JID
  • limit — How many recent messages the server checks for your add-ons
Returns:
  • Vec<NewsletterMyAddOns> — One entry per message you reacted to or voted on. Messages without any of your add-ons are not returned.
Poll tallies on NewsletterMessage::votes and in live updates count every follower’s votes. They can’t tell you whether your vote landed or what your current selection is. Use get_my_addons to read that back, for example after calling send_poll_vote. A removed vote is still returned. Its poll_vote is Some with an empty option_hashes list. poll_vote is None only if you never voted on that message. Errors: get_my_addons returns NewsletterError::InvalidRequest before sending if jid is not a newsletter JID. A malformed response returns NewsletterError::Iq(IqError::ParseError(_)), preserving its parsing cause. Do not depend on parser display text. Example:

subscribe_live_updates

Subscribe to live updates for a newsletter (reaction counts, forward counts, poll tallies).
Parameters:
  • jid — Newsletter JID
Returns:
  • u64 — Subscription duration in seconds (typically 300)
The server sends Event::NewsletterLiveUpdate events with updated reaction counts, forward counts, and poll tallies. See NewsletterLiveUpdate for the event fields. You need to re-subscribe periodically when the duration expires. Example:

Types

NewsletterMetadata

Metadata for a newsletter channel. Implements PartialEq and Eq for direct comparison.
Viewer mute fields:
  • muted — Whether you have muted the channel’s updates. This is the “Mute” toggle a follower sees in WhatsApp. None when the server sent no setting, which is what a non-follower sees.
  • follower_activity_muted — Whether you have muted follower-activity notifications. Only owners and admins receive these, so followers see None.
Both fields are populated from viewer_metadata.settings on list_subscribed and get_metadata. Only ON and OFF are accepted; any missing or unknown value stays None.

NewsletterVerification

The server’s verification value is matched ignoring case, so VERIFIED reads as Verified. An absent field reads as Unverified. Any other value is kept verbatim in Other.

NewsletterState

The server’s state.type value is matched ignoring case, so ACTIVE, SUSPENDED, and GEOSUSPENDED map to their variants. An absent state reads as Active. Any other value is kept verbatim in Other. For example, a deleted channel reads as Other("DELETED").

NewsletterRole

The viewer’s role in a newsletter.
All newsletter enums are #[non_exhaustive], so match statements should include a wildcard arm to handle future variants.

NewsletterAdminProfile

An admin’s public profile within a newsletter, carried on NewsletterAdminInfo and on a NewsletterFollower who is an admin.
id is the profile’s own identifier, not a JID — the server hands it back as an opaque string and WhatsApp Web never parses it.

NewsletterAdminInfo

Admin-side information about a newsletter, returned by get_admin_info.
  • admin_count — How many admins the newsletter has. The server only answers this for admins and owners, so it’s None for everyone else — never a 0 that would misleadingly read as “no admins”.
  • admin_profile — The viewer’s own admin profile, present once they’ve set one up.
  • admin_profiles_enabled — Whether admin profiles are enabled for this newsletter.

NewsletterFollower

A follower (subscriber) of a newsletter, returned by get_followers.
  • jid — The follower’s identity JID — a LID on accounts that have migrated.
  • phone_jid — The follower’s phone-number JID, withheld by the server when the follower’s privacy settings hide it.
  • role — The follower’s role in the newsletter.
  • follow_time — When the follower subscribed (Unix seconds).
  • admin_profile — Set when the follower is an admin who has published a profile.

NewsletterMessage

A message from a newsletter’s history. #[non_exhaustive] — construct it from parsed responses rather than by struct literal, so newly-added counters land as fields, not breaks.
  • message_id — Optional client content ID. It stays absent when the server omitted it. Use message_ref(chat) for actions; edits/revokes require this domain, while reactions require the server content ID.
  • server_id — Server-assigned monotonic id. Use it as the pagination cursor in get_messages and as the target for send_reaction.
  • timestamp — Message timestamp (Unix seconds). Moves with an edit.
  • message_type — Text, Media, or Poll on the wire. An edit or revocation is not a message type; see edit. Reactions are counts on the reacted-to message, not a type. A message without a type attribute reads as Text.
  • message_type_raw — The type attribute exactly as the server sent it. None when the attribute was absent. Use it to tell an absent type from an explicit text, or to relay the server’s token verbatim.
  • edit — AdminEdit (3) for an edited message, AdminRevoke (8) for a revoked one, Empty otherwise. A revoked message keeps its envelope, drops its body, its forward counter, and message becomes None.
  • message — Decoded protobuf message from the <plaintext> bytes. None when the server sent no body (for example, after a revocation, or when the payload was empty).
  • media_type — Hint about the payload readable without decoding it. Unknown values are kept as NewsletterMediaType::Other.
  • reactions — Aggregated per-emoji counts.
  • votes — Per-option tallies when this is a poll message; empty otherwise. See NewsletterPollVote.
  • forwards_count — How many times the message was forwarded. None means the server omitted the counter; treat it as distinct from Some(0).
  • views_count, responses_count — View count and channel-question response count. Contract-derived; a follower-side capture never carried them, so expect None unless the viewer’s role in the channel entitles them to those figures.
  • original_timestamp — <meta original_msg_t> in Unix seconds: when the message was first posted, as opposed to timestamp, which moves with an edit.
  • last_edit_timestamp_ms — <meta msg_edit_t> in Unix milliseconds (unlike every other timestamp on this struct): when the message was last edited.
  • poll_type — The poll’s lifecycle stage (creation, quiz creation, result snapshot). Populated only when message_type is Poll, matching WhatsApp Web’s scoping. A stage PollType has no variant for reads as None.
  • poll_type_raw — <meta polltype> exactly as the server sent it. It is set whatever the message type, and whether or not PollType models the value. None when the server sent no polltype.
  • content_type, question_type, message_association_type — <meta> classifiers. question_type is question for a channel question or reply for an admin reply. message_association_type records how this media relates to another message (media_poll, motion_photo, poll_add_option, …).
  • is_wamo_sub — <meta is_wamo_sub="true">.
  • admin_profile — Publishing admin’s public profile, on channels that enabled admin profiles.
  • rcat — <rcat> payload bytes. Opaque receiver-side token WhatsApp Web associates with type="media" + mediatype="url" link previews; carried verbatim and not interpreted.

NewsletterMessageType

The type attribute of a newsletter history <message>. Uses a wire enum with an Other(String) fallback so a future value on the wire deserializes rather than fails.
Only Text, Media, and Poll are produced by the wire; the other named variants are kept for source compatibility with earlier releases and will never match a parsed response. Read edits and revocations from NewsletterMessage::edit, poll stage from NewsletterMessage::poll_type, poll totals from NewsletterMessage::votes, and reaction totals from NewsletterMessage::reactions. Methods:
  • as_str() — Wire-protocol string representation.
  • From<&str> — Parse from a wire string; unknown values become Other(String).

NewsletterMediaType

The mediatype attribute of newsletter <plaintext>. Known values match the whatspec IR; anything else is kept as Other(String) for forward compatibility.

NewsletterQuestionType

The questiontype attribute in newsletter <meta>.

NewsletterMessageAssociationType

The message_association_type attribute in newsletter <meta>. Describes how a media message relates to another message.

NewsletterReactionCount

A reaction count on a newsletter message.

NewsletterPollVote

A poll option’s vote tally on a newsletter message. Channel polls are counted server-side, so unlike a DM or group vote the totals arrive in plaintext — nothing to decrypt.
Votes with malformed hashes or a missing or malformed required count are dropped during parsing; a valid count="0" is preserved and remains distinct from absence.

NewsletterMyAddOns

Your own add-ons on one newsletter message, as returned by get_my_addons.
  • server_id — The message’s server-assigned ID
  • reaction — Your reaction, or None if you haven’t reacted to this message
  • poll_vote — Your poll vote, or None if you never voted on this message. A removed vote is Some with no option hashes.

NewsletterMyReaction

Your own reaction on a newsletter message.

NewsletterMyPollVote

Your own vote on a newsletter poll.
  • timestamp — When the selection was last sent (Unix seconds). It matches the t of the server’s acknowledgement for that vote.
  • option_hashes — The selected options, each a compute_option_hash of an option name. These are the same keys as NewsletterPollVote::option_hash. Empty after you remove your vote.

Error handling

All newsletter methods return Result<T, NewsletterError>:

Publish a text poll

Use client.newsletter().send_poll(&channel, name, &options, selectable_count).await? for plaintext channel polls. Supply 2 to 12 distinct options and selectable_count 1 for one answer or 0 for multiple answers. A non-channel target returns NewsletterError::InvalidRequest. Poll-definition validation returns NewsletterError::Poll; sending failures retain NewsletterError::Send. The returned SendResult identifies the sent stanza and does not confirm server acceptance. Obtain its newsletter_ref() and later attach the server ID observed in channel history or a live update before voting. Channel polls carry no E2E creation secret. Do not publish a Polls::create result as channel content.