Examples on this page target source snapshot
7e5c198e, not a crates.io release. Use the current API guide to select the matching dependency.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.Vec<NewsletterMetadata>— List of subscribed newsletters
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.jid— Newsletter JID (server must benewsletter)
NewsletterMetadata— Full newsletter metadata
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.invite_code— Newsletter invite code string
NewsletterMetadata— Full newsletter metadata
create
Create a new newsletter.name— Newsletter namedescription— Optional description
NewsletterMetadata— Metadata of the newly created newsletter
join
Join (subscribe to) a newsletter.jid— Newsletter JID to join
NewsletterMetadata— Metadata with the viewer’s role set toSubscriber
leave
Leave (unsubscribe from) a newsletter.jid— Newsletter JID to leave
update
Update a newsletter’s name and/or description.jid— Newsletter JIDname— New name, orNoneto keep the current namedescription— New description, orNoneto keep the current description
NewsletterMetadata— Updated metadata
set_picture
Replace a newsletter’s picture. Carried by the same mutation asupdate — WhatsApp Web edits the picture as base64-encoded JPEG bytes — so the response is the newsletter’s refreshed metadata.
jid— Newsletter JIDjpeg— JPEG-encoded image bytes
NewsletterMetadata— Updated metadata
remove_picture
Remove a newsletter’s picture.jid— Newsletter JID
NewsletterMetadata— Updated metadata
delete
Delete a newsletter. Owner-only.jid— Newsletter JID
change_owner
Transfer a newsletter’s ownership to another user. Owner-only.jid— Newsletter JIDuser— 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 withNewsletterError::InvalidRequestrather than sending an address the server can’t route.
demote_admin
Demote an admin of a newsletter back to subscriber. Owner-only.jid— Newsletter JIDuser— The admin to demote. Same LID resolution aschange_owner.
get_admin_info
Fetch a newsletter’s admin-side information, including its admin count.jid— Newsletter JID
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.get_followers
List up tocount of a newsletter’s followers (subscribers).
jid— Newsletter JIDcount— 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.Vec<NewsletterFollower>— One page of followers
set_follower_mute
Mute or unmute a newsletter’s follower-activity notifications (WhatsApp Web’sMUTE_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.
jid— Newsletter JIDmuted—truesilences notifications,falsere-enables them
set_admin_mute
Mute or unmute a newsletter’s channel updates (WhatsApp Web’sMUTE_ADMIN_ACTIVITY). This is the “Mute” toggle a follower sees on the channel in WhatsApp, and the result is reflected in NewsletterMetadata::muted.
jid— Newsletter JIDmuted—truesilences admin-activity notifications,falsere-enables them
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 unifiedclient.send_message() method. See the Send API reference for full details.
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
UseNewsletterMessageRef 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.
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.jid— Newsletter JIDcount— Maximum number of messages to returnbefore— If set, return messages before thisserver_id(for pagination)
Vec<NewsletterMessage>— List of newsletter messages
get_my_addons
Read your own reaction and poll vote on a newsletter’s recent messages.jid— Newsletter JIDlimit— How many recent messages the server checks for your add-ons
Vec<NewsletterMyAddOns>— One entry per message you reacted to or voted on. Messages without any of your add-ons are not returned.
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).jid— Newsletter JID
u64— Subscription duration in seconds (typically 300)
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. ImplementsPartialEq and Eq for direct comparison.
muted— Whether you have muted the channel’s updates. This is the “Mute” toggle a follower sees in WhatsApp.Nonewhen 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 seeNone.
viewer_metadata.settings on list_subscribed and get_metadata. Only ON and OFF are accepted; any missing or unknown value stays None.
NewsletterVerification
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
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.#[non_exhaustive], so match statements should include a wildcard arm to handle future variants.
NewsletterAdminProfile
An admin’s public profile within a newsletter, carried onNewsletterAdminInfo 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 byget_admin_info.
admin_count— How many admins the newsletter has. The server only answers this for admins and owners, so it’sNonefor everyone else — never a0that 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 byget_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. Usemessage_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 inget_messagesand as the target forsend_reaction.timestamp— Message timestamp (Unix seconds). Moves with an edit.message_type—Text,Media, orPollon the wire. An edit or revocation is not a message type; seeedit. Reactions are counts on the reacted-to message, not a type. A message without atypeattribute reads asText.message_type_raw— Thetypeattribute exactly as the server sent it.Nonewhen the attribute was absent. Use it to tell an absent type from an explicittext, or to relay the server’s token verbatim.edit—AdminEdit(3) for an edited message,AdminRevoke(8) for a revoked one,Emptyotherwise. A revoked message keeps its envelope, drops its body, its forward counter, andmessagebecomesNone.message— Decoded protobuf message from the<plaintext>bytes.Nonewhen 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 asNewsletterMediaType::Other.reactions— Aggregated per-emoji counts.votes— Per-option tallies when this is a poll message; empty otherwise. SeeNewsletterPollVote.forwards_count— How many times the message was forwarded.Nonemeans the server omitted the counter; treat it as distinct fromSome(0).views_count,responses_count— View count and channel-question response count. Contract-derived; a follower-side capture never carried them, so expectNoneunless 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 totimestamp, 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 whenmessage_typeisPoll, matching WhatsApp Web’s scoping. A stagePollTypehas no variant for reads asNone.poll_type_raw—<meta polltype>exactly as the server sent it. It is set whatever the message type, and whether or notPollTypemodels the value.Nonewhen the server sent nopolltype.content_type,question_type,message_association_type—<meta>classifiers.question_typeisquestionfor a channel question orreplyfor an admin reply.message_association_typerecords 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 withtype="media"+mediatype="url"link previews; carried verbatim and not interpreted.
NewsletterMessageType
Thetype 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.
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 becomeOther(String).
NewsletterMediaType
Themediatype attribute of newsletter <plaintext>. Known values match the whatspec IR; anything else is kept as Other(String) for forward compatibility.
NewsletterQuestionType
Thequestiontype attribute in newsletter <meta>.
NewsletterMessageAssociationType
Themessage_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.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 byget_my_addons.
server_id— The message’s server-assigned IDreaction— Your reaction, orNoneif you haven’t reacted to this messagepoll_vote— Your poll vote, orNoneif you never voted on this message. A removed vote isSomewith 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 thetof the server’s acknowledgement for that vote.option_hashes— The selected options, each acompute_option_hashof an option name. These are the same keys asNewsletterPollVote::option_hash. Empty after you remove your vote.
Error handling
All newsletter methods returnResult<T, NewsletterError>:
Publish a text poll
Useclient.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.