> ## Documentation Index
> Fetch the complete documentation index at: https://whatsapp-rust.jlucaso.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Newsletter

> 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, 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](/api/send#send_message). Reactions remain on the `Newsletter` struct because they use a different stanza format.

<Note>
  Newsletter messages are **plaintext** — they are not encrypted with the Signal protocol.
</Note>

## Access

Access newsletter operations through the client:

```rust theme={null}
let newsletter = client.newsletter();
```

## Methods

### list\_subscribed

List all newsletters the user is subscribed to.

```rust theme={null}
pub async fn list_subscribed(&self) -> Result<Vec<NewsletterMetadata>, NewsletterError>
```

**Returns:**

* `Vec<NewsletterMetadata>` — List of subscribed newsletters

**Example:**

```rust theme={null}
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 theme={null}
pub async fn get_metadata(&self, jid: &Jid) -> Result<NewsletterMetadata, NewsletterError>
```

**Parameters:**

* `jid` — Newsletter JID (server must be `newsletter`)

**Returns:**

* `NewsletterMetadata` — Full newsletter metadata

**Example:**

```rust theme={null}
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 theme={null}
pub async fn get_metadata_by_invite(
    &self,
    invite_code: &str,
) -> Result<NewsletterMetadata, NewsletterError>
```

**Parameters:**

* `invite_code` — Newsletter invite code string

**Returns:**

* `NewsletterMetadata` — Full newsletter metadata

**Example:**

```rust theme={null}
let metadata = client.newsletter()
    .get_metadata_by_invite("ABC123")
    .await?;

println!("Found: {} ({})", metadata.name, metadata.jid);
```

### create

Create a new newsletter.

```rust theme={null}
pub async fn create(
    &self,
    name: &str,
    description: Option<&str>,
) -> Result<NewsletterMetadata, NewsletterError>
```

**Parameters:**

* `name` — Newsletter name
* `description` — Optional description

**Returns:**

* `NewsletterMetadata` — Metadata of the newly created newsletter

**Example:**

```rust theme={null}
let created = client.newsletter()
    .create("My Channel", Some("A description"))
    .await?;

println!("Created: {} ({})", created.name, created.jid);
```

### join

Join (subscribe to) a newsletter.

```rust theme={null}
pub async fn join(&self, jid: &Jid) -> Result<NewsletterMetadata, NewsletterError>
```

**Parameters:**

* `jid` — Newsletter JID to join

**Returns:**

* `NewsletterMetadata` — Metadata with the viewer's role set to `Subscriber`

**Example:**

```rust theme={null}
let joined = client.newsletter().join(&newsletter_jid).await?;

println!("Joined '{}' as {:?}", joined.name, joined.role);
```

### leave

Leave (unsubscribe from) a newsletter.

```rust theme={null}
pub async fn leave(&self, jid: &Jid) -> Result<(), NewsletterError>
```

**Parameters:**

* `jid` — Newsletter JID to leave

**Example:**

```rust theme={null}
client.newsletter().leave(&newsletter_jid).await?;
```

### update

Update a newsletter's name and/or description.

```rust theme={null}
pub async fn update(
    &self,
    jid: &Jid,
    name: Option<&str>,
    description: Option<&str>,
) -> Result<NewsletterMetadata, NewsletterError>
```

**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 theme={null}
let updated = client.newsletter()
    .update(&newsletter_jid, Some("New Name"), None)
    .await?;

println!("Updated: {}", updated.name);
```

### 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.

```rust theme={null}
pub async fn set_follower_mute(&self, jid: &Jid, muted: bool) -> Result<(), NewsletterError>
```

**Parameters:**

* `jid` — Newsletter JID
* `muted` — `true` silences notifications, `false` re-enables them

**Example:**

```rust theme={null}
// Mute
client.newsletter().set_follower_mute(&newsletter_jid, true).await?;

// Unmute
client.newsletter().set_follower_mute(&newsletter_jid, false).await?;
```

### set\_admin\_mute

Mute or unmute a newsletter's **admin-activity** notifications (WhatsApp Web's `MUTE_ADMIN_ACTIVITY`). Only meaningful for owners/admins.

```rust theme={null}
pub async fn set_admin_mute(&self, jid: &Jid, muted: bool) -> Result<(), NewsletterError>
```

**Parameters:**

* `jid` — Newsletter JID
* `muted` — `true` silences admin-activity notifications, `false` re-enables them

**Example:**

```rust theme={null}
client.newsletter().set_admin_mute(&newsletter_jid, true).await?;
```

<Note>
  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`.
</Note>

### Sending messages

Newsletter message sending is handled by the unified `client.send_message()` method. See the [Send API reference](/api/send#send_message) for full details.

```rust theme={null}
use waproto::whatsapp as wa;

let message = wa::Message {
    conversation: Some("Hello subscribers!".to_string()),
    ..Default::default()
};

// Pass a newsletter JID directly to send_message
let msg_id = client.send_message(newsletter_jid, message).await?;
```

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.

<Note>
  `Newsletter::send_message()` was removed. Use `client.send_message()` instead — it accepts newsletter, group, and direct message JIDs.
</Note>

### send\_reaction

Send a reaction to a newsletter message.

```rust theme={null}
pub async fn send_reaction(
    &self,
    jid: &Jid,
    server_id: u64,
    reaction: &str,
) -> Result<(), NewsletterError>
```

**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 theme={null}
// Add a reaction
client.newsletter()
    .send_reaction(&newsletter_jid, server_id, "👍")
    .await?;

// Remove a reaction
client.newsletter()
    .send_reaction(&newsletter_jid, server_id, "")
    .await?;
```

### edit\_message

Edit a previously-sent newsletter message. Channel messages are plaintext, so the edit is sent as a `<message edit="1">` stanza with the new protobuf body — not through the E2E send path.

```rust theme={null}
pub async fn edit_message(
    &self,
    jid: &Jid,
    message_id: impl Into<String>,
    new_content: wa::Message,
) -> Result<(), NewsletterError>
```

**Parameters:**

* `jid` — Newsletter JID. Non-newsletter JIDs are rejected with an error; use [`Client::edit_message`](/api/send) for DMs and groups.
* `message_id` — The target message's `message_id` (the wire stanza id, as returned by `send_message` or carried on [`NewsletterMessage`](#newslettermessage)). This is **not** the `server_id` used by reactions. Empty IDs are rejected.
* `new_content` — Replacement message body. Typically a `wa::Message { conversation: Some(..), .. }` for text edits.

**Example:**

```rust theme={null}
use waproto::whatsapp as wa;

let new_body = wa::Message {
    conversation: Some("edited text".to_string()),
    ..Default::default()
};

client.newsletter()
    .edit_message(&newsletter_jid, original_message_id, new_body)
    .await?;
```

### revoke\_message

Revoke (delete) a previously-sent newsletter message. Like [`edit_message`](#edit_message), this goes through the plaintext channel path, not the E2E send path.

```rust theme={null}
pub async fn revoke_message(
    &self,
    jid: &Jid,
    message_id: impl Into<String>,
) -> Result<(), NewsletterError>
```

**Parameters:**

* `jid` — Newsletter JID. Non-newsletter JIDs are rejected; use [`Client::revoke_message`](/api/send) for DMs and groups.
* `message_id` — The target message's `message_id` (wire stanza id, not `server_id`). Empty IDs are rejected.

**Example:**

```rust theme={null}
client.newsletter()
    .revoke_message(&newsletter_jid, message_id)
    .await?;
```

<Note>
  Newsletter JIDs are now also rejected at the root of the E2E send path. If your code accidentally routes a channel JID through `send_message_impl`, `pin_message`, or the standard `edit_message` / `revoke_message` on `Client`, you get an error that names the mis-route instead of a malformed encrypted fan-out.
</Note>

### get\_messages

Fetch message history from a newsletter.

```rust theme={null}
pub async fn get_messages(
    &self,
    jid: &Jid,
    count: u32,
    before: Option<u64>,
) -> Result<Vec<NewsletterMessage>, NewsletterError>
```

**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:**

```rust theme={null}
// 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 theme={null}
pub async fn subscribe_live_updates(
    &self,
    jid: &Jid,
) -> Result<u64, NewsletterError>
```

**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 theme={null}
let duration = client.newsletter()
    .subscribe_live_updates(&newsletter_jid)
    .await?;

println!("Subscribed for {}s", duration);
```

## Types

### NewsletterMetadata

Metadata for a newsletter channel. Implements `PartialEq` and `Eq` for direct comparison.

```rust theme={null}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NewsletterMetadata {
    pub jid: Jid,
    pub name: String,
    pub description: Option<String>,
    pub subscriber_count: u64,
    pub verification: NewsletterVerification,
    pub state: NewsletterState,
    pub picture_url: Option<String>,
    pub preview_url: Option<String>,
    pub invite_code: Option<String>,
    pub role: Option<NewsletterRole>,
    pub creation_time: Option<u64>,
}
```

### NewsletterVerification

```rust theme={null}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NewsletterVerification {
    Verified,
    Unverified,
}
```

### NewsletterState

```rust theme={null}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NewsletterState {
    Active,
    Suspended,
    Geosuspended,
}
```

### NewsletterRole

The viewer's role in a newsletter.

```rust theme={null}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NewsletterRole {
    Owner,
    Admin,
    Subscriber,
    Guest,
}
```

All newsletter enums are `#[non_exhaustive]`, so match statements should include a wildcard arm to handle future variants.

### NewsletterMessage

A message from a newsletter's history.

```rust theme={null}
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, Reaction, etc.).
    pub message_type: NewsletterMessageType,
    /// Whether the viewer is the sender.
    pub is_sender: bool,
    /// Decoded protobuf message (from plaintext bytes).
    pub message: Option<wa::Message>,
    /// Reaction counts on this message.
    pub reactions: Vec<NewsletterReactionCount>,
}
```

### NewsletterMessageType

The type of a newsletter message. Uses a `StringEnum` for type-safe wire-protocol mapping. Implements `PartialEq` and `Eq`.

```rust theme={null}
#[non_exhaustive]
pub enum NewsletterMessageType {
    Text,          // "text"
    Media,         // "media"
    Reaction,      // "reaction"
    Revoke,        // "revoke"
    PollCreation,  // "poll_creation"
    PollVote,      // "poll_vote"
    Edit,          // "edit"
    Other(String), // Unknown/future types
}
```

**Methods:**

* `as_str()` - Returns the wire-protocol string representation
* `From<&str>` - Parse from wire string, unknown values become `Other(String)`

### NewsletterReactionCount

A reaction count on a newsletter message.

```rust theme={null}
pub struct NewsletterReactionCount {
    pub code: String,
    pub count: u64,
}
```

## Error handling

All newsletter methods return `Result<T, NewsletterError>`:

```rust theme={null}
#[non_exhaustive]
pub enum NewsletterError {
    #[error("{0}")]
    Mex(#[from] MexError),
    #[error("{0}")]
    Iq(#[from] IqError),
    #[error("{0}")]
    Client(#[from] ClientError),
    #[error("invalid newsletter request: {0}")]
    InvalidRequest(String),
    #[error("{0}")]
    Internal(#[from] anyhow::Error),
}
```

```rust theme={null}
use whatsapp_rust::NewsletterError;

match client.newsletter().join(&newsletter_jid).await {
    Ok(metadata) => println!("Joined: {}", metadata.jid),
    Err(NewsletterError::Mex(e)) => eprintln!("MEX error: {}", e),
    Err(NewsletterError::Iq(e)) => eprintln!("IQ error: {}", e),
    Err(e) => eprintln!("Error: {}", e),
}
```
