|
| 1 | +# Wasender Python SDK: Sending Messages to WhatsApp Channels |
| 2 | + |
| 3 | +This document explains how to send messages to WhatsApp Channels using the Wasender Python SDK. |
| 4 | + |
| 5 | +## SDK Version: [Specify Python SDK Version Here, e.g., 0.1.0] |
| 6 | + |
| 7 | +## Overview |
| 8 | + |
| 9 | +Sending a message to a WhatsApp Channel utilizes the existing generic `client.send()` method from the Wasender Python SDK. The key differences compared to sending a message to a regular user or group are: |
| 10 | + |
| 11 | +1. **Recipient (`to` field):** The `to` field in the message payload must be the unique **Channel ID** (also known as Channel JID). This typically looks like `12345678901234567890@newsletter`. |
| 12 | +2. **Message Type Restriction:** Currently, the Wasender API (and thus the SDK) generally **only supports sending text messages** to channels. Attempting to send other message types (images, videos, documents, etc.) to channels via the standard `send()` method may not be supported or could result in an API error. Always refer to the latest official Wasender API documentation for channel messaging capabilities. |
| 13 | + |
| 14 | +## Prerequisites |
| 15 | + |
| 16 | +1. **Obtain a Channel ID:** You need the specific ID of the channel you want to send a message to. One way to obtain a Channel ID is by listening for webhook events (e.g., `message.upsert` or `message.created` if your provider supports it for channels), as this event data for messages originating from a channel will include the channel's JID. |
| 17 | +2. **SDK Initialization:** Ensure the Wasender Python SDK is correctly initialized in your project. This involves: |
| 18 | + * Installing the SDK: `pip install wasenderapi` |
| 19 | + * Setting the environment variable `WASENDER_API_KEY`. This API key should correspond to the specific WhatsApp session you intend to use for sending channel messages. |
| 20 | + * Creating an instance of `WasenderClient`. |
| 21 | + (Refer to `README.md` or `docs/messages.md` for detailed SDK initialization examples.) |
| 22 | + |
| 23 | +## How to Send a Message to a Channel |
| 24 | + |
| 25 | +You will use the `client.send()` method with a `TextPayload` Pydantic model. The Python SDK also provides a specific type alias `wasenderapi.models.channel.ChannelTextMessage` which is an alias for `TextPayload`, intended for conceptual clarity when working with channels. |
| 26 | + |
| 27 | +### Python Model for Channel Messages |
| 28 | + |
| 29 | +The relevant Pydantic model from `wasenderapi.models.channel` is: |
| 30 | + |
| 31 | +```python |
| 32 | +# From wasenderapi/models/channel.py |
| 33 | +# (Conceptual - actual import would be from wasenderapi.models) |
| 34 | +from ..models.message import TextPayload # Relative import path for illustration |
| 35 | + |
| 36 | +ChannelTextMessage = TextPayload |
| 37 | +``` |
| 38 | +This means you can directly use `TextPayload` from `wasenderapi.models` when constructing your message for a channel. |
| 39 | + |
| 40 | +### Code Example |
| 41 | + |
| 42 | +Here's how you can send a text message to a WhatsApp Channel in Python: |
| 43 | + |
| 44 | +```python |
| 45 | +# examples/send_channel_message.py |
| 46 | +import asyncio |
| 47 | +import os |
| 48 | +import logging |
| 49 | +from datetime import datetime |
| 50 | +from typing import Optional |
| 51 | + |
| 52 | +from wasenderapi import WasenderClient |
| 53 | +from wasenderapi.errors import WasenderAPIError |
| 54 | +from wasenderapi.models import ( |
| 55 | + TextPayload, # Equivalent to ChannelTextMessage |
| 56 | + WasenderSendResult, |
| 57 | + RateLimitInfo |
| 58 | +) |
| 59 | + |
| 60 | +# Configure basic logging |
| 61 | +logging.basicConfig(level=logging.INFO) |
| 62 | +logger = logging.getLogger(__name__) |
| 63 | + |
| 64 | +# --- SDK Initialization (Minimal for this example) --- |
| 65 | +api_key = os.getenv("WASENDER_API_KEY") # API Key for the session sending the message |
| 66 | + |
| 67 | +if not api_key: |
| 68 | + logger.error("Error: WASENDER_API_KEY environment variable is not set.") |
| 69 | + exit(1) |
| 70 | + |
| 71 | +# For sending messages via an existing session, typically only the session's API key is needed. |
| 72 | +client = WasenderClient(api_key=api_key) |
| 73 | + |
| 74 | +# The personal_access_token is generally used for account-level session management (create, list, delete sessions), |
| 75 | +# not for sending messages through an already established session. |
| 76 | +# # personal_access_token = os.getenv("WASENDER_PERSONAL_ACCESS_TOKEN") # Token for account-level session operations |
| 77 | +# # client = WasenderClient(api_key=api_key, personal_access_token=personal_access_token) # If needed for specific advanced scenarios or different auth models |
| 78 | + |
| 79 | +# --- Helper to log rate limits (can be imported from a common utils module) --- |
| 80 | +def log_rate_limit_info(rate_limit: Optional[RateLimitInfo]): |
| 81 | + if rate_limit: |
| 82 | + reset_time_str = datetime.fromtimestamp(rate_limit.reset_timestamp).strftime('%Y-%m-%d %H:%M:%S') if rate_limit.reset_timestamp else "N/A" |
| 83 | + logger.info( |
| 84 | + f"Rate Limit Info: Remaining = {rate_limit.remaining}, Limit = {rate_limit.limit}, Resets at = {reset_time_str}" |
| 85 | + ) |
| 86 | + else: |
| 87 | + logger.info("Rate limit information not available for this request.") |
| 88 | + |
| 89 | +# --- Generic Error Handler (can be imported) --- |
| 90 | +def handle_channel_api_error(error: Exception, operation: str, channel_jid: Optional[str] = None): |
| 91 | + target = f" for channel {channel_jid}" if channel_jid else "" |
| 92 | + if isinstance(error, WasenderAPIError): |
| 93 | + logger.error(f"API Error during {operation}{target}:") |
| 94 | + logger.error(f" Message: {error.message}") |
| 95 | + logger.error(f" Status Code: {error.status_code or 'N/A'}") |
| 96 | + if error.api_message: logger.error(f" API Message: {error.api_message}") |
| 97 | + if error.error_details: logger.error(f" Error Details: {error.error_details}") |
| 98 | + if error.rate_limit: log_rate_limit_info(error.rate_limit) |
| 99 | + else: |
| 100 | + logger.error(f"An unexpected error occurred during {operation}{target}: {error}") |
| 101 | + |
| 102 | +# --- Main function to send message to channel --- |
| 103 | +async def send_message_to_channel_example(channel_jid: str, message_text: str): |
| 104 | + logger.info(f"\\n--- Attempting to Send Message to Channel: {channel_jid} ---") |
| 105 | + if not channel_jid: |
| 106 | + logger.error("Channel JID is required.") |
| 107 | + return |
| 108 | + if not message_text: |
| 109 | + logger.error("Message text is required.") |
| 110 | + return |
| 111 | + |
| 112 | + # Use TextPayload (aliased as ChannelTextMessage in the SDK for conceptual clarity) |
| 113 | + channel_payload = TextPayload( |
| 114 | + to=channel_jid, |
| 115 | + text=message_text, |
| 116 | + # messageType defaults to "text" in TextPayload model, so explicit set is optional |
| 117 | + # but good for clarity with channels: message_type="text" |
| 118 | + ) |
| 119 | + |
| 120 | + try: |
| 121 | + result = await client.send(payload=channel_payload) |
| 122 | + send_result: WasenderSendResult = result.response.data |
| 123 | + |
| 124 | + logger.info(f"Message sent to channel {channel_jid} successfully.") |
| 125 | + logger.info(f" Message ID: {send_result.message_id}") |
| 126 | + logger.info(f" Status: {send_result.status}") |
| 127 | + if send_result.detail: |
| 128 | + logger.info(f" Detail: {send_result.detail}") |
| 129 | + |
| 130 | + log_rate_limit_info(result.rate_limit) |
| 131 | + |
| 132 | + except Exception as e: |
| 133 | + handle_channel_api_error(e, "sending message", channel_jid=channel_jid) |
| 134 | + |
| 135 | +async def main(): |
| 136 | + # Replace with the actual Channel ID you want to send a message to |
| 137 | + target_channel_jid = "12345678901234567890@newsletter" # Example JID |
| 138 | + message = "Hello Channel! This is a test message from the Python SDK." |
| 139 | + |
| 140 | + if target_channel_jid == "12345678901234567890@newsletter": |
| 141 | + logger.warning("Please replace `target_channel_jid` with a real Channel ID before running.") |
| 142 | + else: |
| 143 | + await send_message_to_channel_example(target_channel_jid, message) |
| 144 | + |
| 145 | + # Example for another channel or message: |
| 146 | + # another_channel_jid = "09876543210987654321@newsletter" |
| 147 | + # await send_message_to_channel_example(another_channel_jid, "Another important update!") |
| 148 | + |
| 149 | +if __name__ == "__main__": |
| 150 | + # Before running, ensure WASENDER_API_KEY is set in your environment. |
| 151 | + # Also, replace target_channel_jid in main() with a valid Channel ID. |
| 152 | + logger.info("Starting channel message example. Ensure JID and API Key are set.") |
| 153 | + asyncio.run(main()) |
| 154 | + |
| 155 | +``` |
| 156 | + |
| 157 | +### Key Points from the Example: |
| 158 | + |
| 159 | +- **`to`**: Set to the `target_channel_jid`. |
| 160 | +- **`TextPayload`**: Used to construct the message. The `message_type` attribute within `TextPayload` defaults to `"text"`, which is what channels typically require. You can explicitly set `message_type="text"` for clarity if desired. |
| 161 | +- **`text`**: Contains the content of your message. |
| 162 | +- The example includes minimal SDK initialization, error handling, and rate limit logging. |
| 163 | + |
| 164 | +## Important Considerations |
| 165 | + |
| 166 | +- **Channel ID Accuracy:** Ensure the Channel ID (ending in `@newsletter`) is correct. Sending to an incorrect ID will fail. |
| 167 | +- **Message Content:** As emphasized, typically only text messages are supported for channels via this standard send method. Sending other types will likely result in an API error. Always verify with current API documentation. |
| 168 | +- **API Limitations:** The ability to send messages to channels, supported message types, and any other restrictions are determined by the underlying Wasender API. Refer to the official Wasender API documentation for the most up-to-date information. |
| 169 | +- **Webhook for Channel IDs:** Using webhooks to listen for relevant message events (like `message.created` or `message.upsert`) is a practical way to discover Channel IDs your connected number interacts with or is part of, especially if these channels are not self-created. |
| 170 | + |
| 171 | +This guide should provide you with the necessary information to send text messages to WhatsApp Channels using the Wasender Python SDK. |
0 commit comments