From 693d0cdadc59bc80f18df17171d0b4dc7e073023 Mon Sep 17 00:00:00 2001
From: "mintlify[bot]" <109931778+mintlify[bot]@users.noreply.github.com>
Date: Wed, 18 Mar 2026 21:19:00 +0000
Subject: [PATCH] =?UTF-8?q?Document=20newsletter=20(channel)=20support=20?=
=?UTF-8?q?=E2=80=94=20guide,=20API=20reference,=20and=20events?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Generated-By: mintlify-agent
---
api/newsletter.mdx | 422 +++++++++++++++++++++++++++++++++++++++++
concepts/events.mdx | 51 +++++
docs.json | 2 +
guides/newsletters.mdx | 262 +++++++++++++++++++++++++
4 files changed, 737 insertions(+)
create mode 100644 api/newsletter.mdx
create mode 100644 guides/newsletters.mdx
diff --git a/api/newsletter.mdx b/api/newsletter.mdx
new file mode 100644
index 00000000..70cf9cc3
--- /dev/null
+++ b/api/newsletter.mdx
@@ -0,0 +1,422 @@
+---
+title: Newsletter
+description: Newsletter (channel) operations — create, manage, subscribe, and send messages to WhatsApp channels
+---
+
+The `Newsletter` feature provides methods for managing WhatsApp newsletter channels, including creation, subscription management, messaging, reactions, and live updates. Newsletter operations use MEX (GraphQL) for metadata/management and IQ stanzas for message operations.
+
+
+Newsletter messages are **plaintext** — they are not encrypted with the Signal protocol.
+
+
+## Access
+
+Access newsletter operations through the client:
+
+```rust
+let newsletter = client.newsletter();
+```
+
+## Methods
+
+### list_subscribed
+
+List all newsletters the user is subscribed to.
+
+```rust
+pub async fn list_subscribed(&self) -> Result, MexError>
+```
+
+**Returns:**
+- `Vec` — List of subscribed newsletters
+
+**Example:**
+```rust
+let newsletters = client.newsletter().list_subscribed().await?;
+
+for nl in &newsletters {
+ println!("{}: {} ({} subscribers)", nl.jid, nl.name, nl.subscriber_count);
+}
+```
+
+### get_metadata
+
+Fetch metadata for a newsletter by its JID.
+
+```rust
+pub async fn get_metadata(&self, jid: &Jid) -> Result
+```
+
+**Parameters:**
+- `jid` — Newsletter JID (server must be `newsletter`)
+
+**Returns:**
+- `NewsletterMetadata` — Full newsletter metadata
+
+**Example:**
+```rust
+let metadata = client.newsletter().get_metadata(&newsletter_jid).await?;
+
+println!("Name: {}", metadata.name);
+println!("Subscribers: {}", metadata.subscriber_count);
+println!("Verified: {:?}", metadata.verification);
+```
+
+### get_metadata_by_invite
+
+Fetch metadata for a newsletter by its invite code.
+
+```rust
+pub async fn get_metadata_by_invite(
+ &self,
+ invite_code: &str,
+) -> Result
+```
+
+**Parameters:**
+- `invite_code` — Newsletter invite code string
+
+**Returns:**
+- `NewsletterMetadata` — Full newsletter metadata
+
+**Example:**
+```rust
+let metadata = client.newsletter()
+ .get_metadata_by_invite("ABC123")
+ .await?;
+
+println!("Found: {} ({})", metadata.name, metadata.jid);
+```
+
+### create
+
+Create a new newsletter.
+
+```rust
+pub async fn create(
+ &self,
+ name: &str,
+ description: Option<&str>,
+) -> Result
+```
+
+**Parameters:**
+- `name` — Newsletter name
+- `description` — Optional description
+
+**Returns:**
+- `NewsletterMetadata` — Metadata of the newly created newsletter
+
+**Example:**
+```rust
+let created = client.newsletter()
+ .create("My Channel", Some("A description"))
+ .await?;
+
+println!("Created: {} ({})", created.name, created.jid);
+```
+
+### join
+
+Join (subscribe to) a newsletter.
+
+```rust
+pub async fn join(&self, jid: &Jid) -> Result
+```
+
+**Parameters:**
+- `jid` — Newsletter JID to join
+
+**Returns:**
+- `NewsletterMetadata` — Metadata with the viewer's role set to `Subscriber`
+
+**Example:**
+```rust
+let joined = client.newsletter().join(&newsletter_jid).await?;
+
+println!("Joined '{}' as {:?}", joined.name, joined.role);
+```
+
+### leave
+
+Leave (unsubscribe from) a newsletter.
+
+```rust
+pub async fn leave(&self, jid: &Jid) -> Result<(), MexError>
+```
+
+**Parameters:**
+- `jid` — Newsletter JID to leave
+
+**Example:**
+```rust
+client.newsletter().leave(&newsletter_jid).await?;
+```
+
+### update
+
+Update a newsletter's name and/or description.
+
+```rust
+pub async fn update(
+ &self,
+ jid: &Jid,
+ name: Option<&str>,
+ description: Option<&str>,
+) -> Result
+```
+
+**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:**
+```rust
+let updated = client.newsletter()
+ .update(&newsletter_jid, Some("New Name"), None)
+ .await?;
+
+println!("Updated: {}", updated.name);
+```
+
+### send_message
+
+Send a message to a newsletter. Newsletter messages are plaintext (no Signal E2E encryption).
+
+```rust
+pub async fn send_message(
+ &self,
+ jid: &Jid,
+ message: &wa::Message,
+) -> Result
+```
+
+**Parameters:**
+- `jid` — Newsletter JID
+- `message` — Protobuf message to send
+
+**Returns:**
+- `String` — Client-assigned message ID
+
+**Example:**
+```rust
+use waproto::whatsapp as wa;
+
+let message = wa::Message {
+ conversation: Some("Hello subscribers!".to_string()),
+ ..Default::default()
+};
+
+let msg_id = client.newsletter()
+ .send_message(&newsletter_jid, &message)
+ .await?;
+```
+
+
+For media messages (images, videos, etc.), the media must be uploaded separately using the newsletter-specific upload endpoint before sending. Text messages work directly.
+
+
+### send_reaction
+
+Send a reaction to a newsletter message.
+
+```rust
+pub async fn send_reaction(
+ &self,
+ jid: &Jid,
+ server_id: u64,
+ reaction: &str,
+) -> Result<(), anyhow::Error>
+```
+
+**Parameters:**
+- `jid` — Newsletter JID
+- `server_id` — Server-assigned ID of the message to react to
+- `reaction` — Emoji code (e.g., `"👍"`, `"❤️"`), or empty string to remove
+
+**Example:**
+```rust
+// Add a reaction
+client.newsletter()
+ .send_reaction(&newsletter_jid, server_id, "👍")
+ .await?;
+
+// Remove a reaction
+client.newsletter()
+ .send_reaction(&newsletter_jid, server_id, "")
+ .await?;
+```
+
+### get_messages
+
+Fetch message history from a newsletter.
+
+```rust
+pub async fn get_messages(
+ &self,
+ jid: &Jid,
+ count: u32,
+ before: Option,
+) -> Result, anyhow::Error>
+```
+
+**Parameters:**
+- `jid` — Newsletter JID
+- `count` — Maximum number of messages to return
+- `before` — If set, return messages before this `server_id` (for pagination)
+
+**Returns:**
+- `Vec` — List of newsletter messages
+
+**Example:**
+```rust
+// Fetch latest 50 messages
+let messages = client.newsletter()
+ .get_messages(&newsletter_jid, 50, None)
+ .await?;
+
+// Paginate backwards
+if let Some(oldest) = messages.last() {
+ let older = client.newsletter()
+ .get_messages(&newsletter_jid, 50, Some(oldest.server_id))
+ .await?;
+}
+```
+
+### subscribe_live_updates
+
+Subscribe to live updates for a newsletter (reaction counts, message changes).
+
+```rust
+pub async fn subscribe_live_updates(
+ &self,
+ jid: &Jid,
+) -> Result
+```
+
+**Parameters:**
+- `jid` — Newsletter JID
+
+**Returns:**
+- `u64` — Subscription duration in seconds (typically 300)
+
+The server sends `Event::NewsletterLiveUpdate` events with updated reaction counts. You need to re-subscribe periodically when the duration expires.
+
+**Example:**
+```rust
+let duration = client.newsletter()
+ .subscribe_live_updates(&newsletter_jid)
+ .await?;
+
+println!("Subscribed for {}s", duration);
+```
+
+## Types
+
+### NewsletterMetadata
+
+Metadata for a newsletter channel.
+
+```rust
+pub struct NewsletterMetadata {
+ pub jid: Jid,
+ pub name: String,
+ pub description: Option,
+ pub subscriber_count: u64,
+ pub verification: NewsletterVerification,
+ pub state: NewsletterState,
+ pub picture_url: Option,
+ pub preview_url: Option,
+ pub invite_code: Option,
+ pub role: Option,
+ pub creation_time: Option,
+}
+```
+
+### NewsletterVerification
+
+```rust
+pub enum NewsletterVerification {
+ Verified,
+ Unverified,
+}
+```
+
+### NewsletterState
+
+```rust
+pub enum NewsletterState {
+ Active,
+ Suspended,
+ Geosuspended,
+}
+```
+
+### NewsletterRole
+
+The viewer's role in a newsletter.
+
+```rust
+pub enum NewsletterRole {
+ Owner,
+ Admin,
+ Subscriber,
+ Guest,
+}
+```
+
+### NewsletterMessage
+
+A message from a newsletter's history.
+
+```rust
+pub struct NewsletterMessage {
+ /// Server-assigned message ID (monotonic, used for pagination cursors).
+ pub server_id: u64,
+ /// Message timestamp (Unix seconds).
+ pub timestamp: u64,
+ /// Message type ("text", "media", etc.).
+ pub message_type: String,
+ /// Whether the viewer is the sender.
+ pub is_sender: bool,
+ /// Decoded protobuf message (from plaintext bytes).
+ pub message: Option,
+ /// Reaction counts on this message.
+ pub reactions: Vec,
+}
+```
+
+### NewsletterReactionCount
+
+A reaction count on a newsletter message.
+
+```rust
+pub struct NewsletterReactionCount {
+ pub code: String,
+ pub count: u64,
+}
+```
+
+## Error handling
+
+Metadata and management methods return `MexError`:
+
+```rust
+pub enum MexError {
+ PayloadParsing(String),
+ // ... other variants
+}
+```
+
+Message operations (`send_message`, `send_reaction`, `get_messages`, `subscribe_live_updates`) return `anyhow::Error`.
+
+```rust
+match client.newsletter().create("Test", None).await {
+ Ok(metadata) => println!("Created: {}", metadata.name),
+ Err(e) => eprintln!("Failed: {}", e),
+}
+```
diff --git a/concepts/events.mdx b/concepts/events.mdx
index 69c15fcd..77f56302 100644
--- a/concepts/events.mdx
+++ b/concepts/events.mdx
@@ -129,6 +129,9 @@ pub enum Event {
DeviceListUpdate(DeviceListUpdate),
BusinessStatusUpdate(BusinessStatusUpdate),
+ // Newsletter
+ NewsletterLiveUpdate(NewsletterLiveUpdate),
+
// Notification Updates
DisappearingModeChanged(DisappearingModeChanged),
}
@@ -1034,6 +1037,54 @@ pub enum BusinessUpdateType {
}
```
+## Newsletter Events
+
+### NewsletterLiveUpdate
+
+**Emitted:** When reaction counts change or messages are updated on a newsletter you're subscribed to (via `subscribe_live_updates`).
+
+```rust
+#[derive(Debug, Clone, Serialize)]
+pub struct NewsletterLiveUpdate {
+ pub newsletter_jid: Jid,
+ pub messages: Vec,
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub struct NewsletterLiveUpdateMessage {
+ pub server_id: u64,
+ pub reactions: Vec,
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub struct NewsletterLiveUpdateReaction {
+ pub code: String,
+ pub count: u64,
+}
+```
+
+**Fields:**
+- `newsletter_jid` — The newsletter channel this update is for
+- `messages` — List of messages with updated reaction counts
+- `server_id` — Server-assigned message ID
+- `reactions` — Current reaction counts (emoji code and count)
+
+**Example:**
+```rust
+Event::NewsletterLiveUpdate(update) => {
+ println!("Newsletter {} updated:", update.newsletter_jid);
+ for msg in &update.messages {
+ for r in &msg.reactions {
+ println!(" Message {}: {} x{}", msg.server_id, r.code, r.count);
+ }
+ }
+}
+```
+
+
+You must call `client.newsletter().subscribe_live_updates(&jid)` to receive these events. The subscription has a limited duration (typically 300 seconds) and must be renewed periodically.
+
+
## Notification Events
### DisappearingModeChanged
diff --git a/docs.json b/docs.json
index 09c80ec0..28c079f1 100644
--- a/docs.json
+++ b/docs.json
@@ -47,6 +47,7 @@
"guides/receiving-messages",
"guides/media-handling",
"guides/group-management",
+ "guides/newsletters",
"guides/custom-backends"
]
},
@@ -92,6 +93,7 @@
"api/profile",
"api/chat-actions",
"api/mex",
+ "api/newsletter",
"api/tctoken"
]
},
diff --git a/guides/newsletters.mdx b/guides/newsletters.mdx
new file mode 100644
index 00000000..fbbd09b7
--- /dev/null
+++ b/guides/newsletters.mdx
@@ -0,0 +1,262 @@
+---
+title: Newsletters (channels)
+description: Learn how to create, manage, and interact with WhatsApp newsletter channels in whatsapp-rust
+---
+
+## Overview
+
+Newsletters (also called channels) are broadcast-style messaging in WhatsApp. Unlike groups, newsletter messages are **plaintext** (no Signal E2E encryption) and flow one-way from admins to subscribers.
+
+This guide covers creating newsletters, managing subscriptions, sending messages, and handling live updates.
+
+## Accessing the Newsletter API
+
+All newsletter operations are accessed through the `newsletter()` method:
+
+```rust
+let newsletter = client.newsletter();
+```
+
+See [Newsletter API reference](/api/newsletter) for the full API.
+
+## Creating a newsletter
+
+```rust
+let created = client.newsletter()
+ .create("My Channel", Some("Channel description"))
+ .await?;
+
+println!("Created: {} ({})", created.name, created.jid);
+println!("Invite code: {:?}", created.invite_code);
+```
+
+The returned `NewsletterMetadata` contains the channel's JID (with `@newsletter` server), name, subscriber count, and invite code.
+
+See [Newsletter API reference](/api/newsletter#create) for details.
+
+## Listing subscribed newsletters
+
+```rust
+let newsletters = client.newsletter().list_subscribed().await?;
+
+for nl in &newsletters {
+ println!("{}: {} ({} subscribers)",
+ nl.jid, nl.name, nl.subscriber_count);
+}
+```
+
+See [Newsletter API reference](/api/newsletter#list_subscribed) for details.
+
+## Fetching metadata
+
+### By JID
+
+```rust
+let metadata = client.newsletter().get_metadata(&newsletter_jid).await?;
+
+println!("Name: {}", metadata.name);
+println!("Description: {:?}", metadata.description);
+println!("Subscribers: {}", metadata.subscriber_count);
+println!("Verification: {:?}", metadata.verification);
+println!("State: {:?}", metadata.state);
+```
+
+### By invite code
+
+```rust
+let metadata = client.newsletter()
+ .get_metadata_by_invite("invite-code-here")
+ .await?;
+
+println!("Found: {} ({})", metadata.name, metadata.jid);
+```
+
+See [Newsletter API reference](/api/newsletter#get_metadata) for all metadata fields.
+
+## Joining and leaving
+
+### Join a newsletter
+
+```rust
+let joined = client.newsletter().join(&newsletter_jid).await?;
+
+println!("Joined '{}' as {:?}", joined.name, joined.role);
+```
+
+### Leave a newsletter
+
+```rust
+client.newsletter().leave(&newsletter_jid).await?;
+```
+
+See [Newsletter API reference](/api/newsletter#join) for details.
+
+## Updating a newsletter
+
+You can update the name and/or description of a newsletter you own:
+
+```rust
+let updated = client.newsletter()
+ .update(
+ &newsletter_jid,
+ Some("New Channel Name"),
+ Some("Updated description"),
+ )
+ .await?;
+
+println!("Updated: {}", updated.name);
+```
+
+Pass `None` for fields you don't want to change.
+
+See [Newsletter API reference](/api/newsletter#update) for details.
+
+## Sending messages
+
+Newsletter messages are plaintext — they bypass Signal encryption entirely.
+
+### Text messages
+
+```rust
+use waproto::whatsapp as wa;
+
+let message = wa::Message {
+ conversation: Some("Hello subscribers!".to_string()),
+ ..Default::default()
+};
+
+let msg_id = client.newsletter()
+ .send_message(&newsletter_jid, &message)
+ .await?;
+
+println!("Sent message: {}", msg_id);
+```
+
+
+For media messages (images, videos, etc.), you must upload the media separately using the newsletter-specific upload endpoint before sending. Text messages work directly.
+
+
+### Reactions
+
+Send a reaction to a specific newsletter message using its `server_id`:
+
+```rust
+// React with thumbs up
+client.newsletter()
+ .send_reaction(&newsletter_jid, server_id, "👍")
+ .await?;
+
+// Remove reaction (empty string)
+client.newsletter()
+ .send_reaction(&newsletter_jid, server_id, "")
+ .await?;
+```
+
+See [Newsletter API reference](/api/newsletter#send_message) for details.
+
+## Fetching message history
+
+Retrieve past messages with pagination support:
+
+```rust
+// Fetch the latest 50 messages
+let messages = client.newsletter()
+ .get_messages(&newsletter_jid, 50, None)
+ .await?;
+
+for msg in &messages {
+ println!("ID: {}, time: {}, type: {}",
+ msg.server_id, msg.timestamp, msg.message_type);
+
+ if let Some(decoded) = &msg.message {
+ if let Some(text) = &decoded.conversation {
+ println!(" Text: {}", text);
+ }
+ }
+
+ for reaction in &msg.reactions {
+ println!(" {} x{}", reaction.code, reaction.count);
+ }
+}
+```
+
+### Pagination
+
+Use the `server_id` from a previous response to paginate backwards:
+
+```rust
+// Get messages before a specific server_id
+let older = client.newsletter()
+ .get_messages(&newsletter_jid, 20, Some(last_server_id))
+ .await?;
+```
+
+See [Newsletter API reference](/api/newsletter#get_messages) for details.
+
+## Live updates
+
+Subscribe to real-time updates for a newsletter to receive reaction count changes:
+
+```rust
+let duration = client.newsletter()
+ .subscribe_live_updates(&newsletter_jid)
+ .await?;
+
+println!("Subscribed for {}s", duration);
+```
+
+The server sends `NewsletterLiveUpdate` events with updated reaction counts. Handle them in your event handler:
+
+```rust
+use wacore::types::events::Event;
+
+Event::NewsletterLiveUpdate(update) => {
+ println!("Newsletter {} updated:", update.newsletter_jid);
+ for msg in &update.messages {
+ println!(" Message {}: {:?}", msg.server_id,
+ msg.reactions.iter()
+ .map(|r| format!("{} x{}", r.code, r.count))
+ .collect::>()
+ );
+ }
+}
+```
+
+
+The subscription duration (typically 300 seconds) is returned by the server. You need to re-subscribe periodically to continue receiving live updates.
+
+
+See [Events reference](/concepts/events#newsletterliveupdate) for the full event type.
+
+## Error handling
+
+Newsletter operations return `MexError` for metadata/management operations and `anyhow::Error` for message operations:
+
+```rust
+use whatsapp_rust::features::mex::MexError;
+
+match client.newsletter().get_metadata(&jid).await {
+ Ok(metadata) => println!("Found: {}", metadata.name),
+ Err(MexError::PayloadParsing(msg)) => {
+ eprintln!("Parse error: {}", msg);
+ }
+ Err(e) => eprintln!("Error: {}", e),
+}
+```
+
+## Key differences from groups
+
+| Feature | Newsletters | Groups |
+|---------|------------|--------|
+| Encryption | Plaintext | Signal E2E |
+| Messaging | One-way (admins only) | All members |
+| JID server | `@newsletter` | `@g.us` |
+| Reactions | Emoji counts (aggregated) | Per-message |
+| API backend | MEX (GraphQL) | IQ stanzas |
+
+## Next steps
+
+- [Newsletter API reference](/api/newsletter) — Full API details and types
+- [Events](/concepts/events) — Handle newsletter live update events
+- [Sending messages](/guides/sending-messages) — Send messages to groups and contacts
+- [MEX API](/api/mex) — Understand the GraphQL layer used by newsletters