Skip to content

Conversation

@caiobleggi
Copy link

@caiobleggi caiobleggi commented Dec 9, 2025

📋 Description

This PR introduces initial support for WhatsApp Channels (@newsletter) within EvolutionAPI.
Due to protocol limitations in the underlying WhatsApp Web / Baileys architecture, full channel support is not technically possible, but the following improvements were implemented:

✅ Added

  • Support for sending messages to WhatsApp Channels (@newsletter) using all sendMessage variants.
  • New findChannels controller and endpoint, returning detected newsletter JIDs from Baileys message history.
  • Updated createJid and whatsappNumber to correctly identify and handle newsletter JIDs.
  • Channel results now explicitly return pushName: undefined because WhatsApp Web does not provide channel metadata.

⚠️ Technical Limitations (Protocol-Level)

These limitations come directly from the WhatsApp Web MD protocol (implemented by Baileys) and cannot be bypassed:

  • WhatsApp Web does NOT sync channel metadata (name, description, picture).
  • WhatsApp Web does NOT provide channel admin events or follower messages.
  • The Web protocol does NOT sync message deletions or edits for channels.
  • Deleting messages sent to a channel always returns PENDING (server does not support revoke for @newsletter).
  • No retroactive sync is available; only self-sent messages can be detected.

Because of these limitations, the findChannels endpoint returns:

  • remoteJid
  • lastMessageTimestamp
  • pushName: undefined

This ensures consistent behavior without exposing misleading or incorrect metadata.

🔗 Related Issue

Closes:

🧪 Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📚 Documentation update
  • 🔧 Refactoring (no functional changes)
  • ⚡ Performance improvement
  • 🧹 Code cleanup
  • 🔒 Security fix

🧪 Testing

  • Manual testing completed
  • Functionality verified in development environment
  • No breaking changes introduced
  • Sending messages to @newsletter tested successfully
  • Channel detection through stored messages verified
  • Deletion attempts confirmed as unsupported (expected limitation)

📸 Screenshots (if applicable)

✅ Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have manually tested my changes thoroughly
  • I have verified the changes work with different scenarios
  • Any dependent changes have been merged and published

📝 Additional Notes

Summary by Sourcery

Add WhatsApp Channel (@newsletter) support for contact lookup, message status handling, and channel discovery endpoints.

New Features:

  • Support identifying @newsletter JIDs when resolving numbers in WhatsApp integrations.
  • Expose a new fetchChannels/findChannels API endpoint to list detected newsletter channels with pagination.
  • Return newsletter contacts in onWhatsApp lookups with a dedicated type indicating channel JIDs.

Enhancements:

  • Adjust message status mapping to correctly mark self-sent messages to newsletter channels as delivered.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Dec 9, 2025

Reviewer's Guide

Adds initial WhatsApp Channels (@newsletter) support by recognizing newsletter JIDs in JID creation and onWhatsApp checks, mapping outbound channel messages to a delivered status, exposing a new fetchChannels API through chat controller/router that scans stored messages for newsletter JIDs and returns a paginated list, and wiring a new /findChannels route.

Sequence diagram for the new findChannels API flow

sequenceDiagram
  actor Client
  participant ChatRouter
  participant ChatController
  participant WAMonitor
  participant WAInstance as BaileysStartupService
  participant Prisma as PrismaRepository
  participant DB as Database

  Client->>ChatRouter: POST /chat/findChannels
  ChatRouter->>ChatRouter: dataValidate(request, contactValidateSchema)
  ChatRouter->>ChatController: fetchChannels(instanceDto, query)
  ChatController->>WAMonitor: resolve waInstances[instanceName]
  WAMonitor-->>ChatController: WAInstance
  ChatController->>WAInstance: fetchChannels(query)

  WAInstance->>Prisma: message.findMany({instanceId, where, orderBy, select})
  Prisma->>DB: query messages
  DB-->>Prisma: messages[]
  Prisma-->>WAInstance: messages[]

  loop for each message
    WAInstance->>WAInstance: check isJidNewsletter(remoteJid)
    WAInstance->>WAInstance: update channelMap
  end

  WAInstance->>WAInstance: build allChannels, paginate
  WAInstance-->>ChatController: {total, pages, currentPage, limit, records}
  ChatController-->>ChatRouter: response
  ChatRouter-->>Client: 200 OK + channel list
Loading

Class diagram for new WhatsApp channel support and fetchChannels flow

classDiagram
  class BaileysStartupService {
    - string instanceId
    - PrismaRepository prismaRepository
    + fetchChannels(query)
    + mapMessageStatus(message)
  }

  class PrismaRepository {
    + message_findMany(instanceId, orderBy, where, select)
  }

  class ChatController {
    - WAMonitor waMonitor
    + fetchChannels(instanceDto, query)
  }

  class WAMonitor {
    - Map~string, WAInstance~ waInstances
  }

  class WAInstance {
    + fetchChannels(query)
  }

  class ChatRouter {
    - ChatController chatController
    + configureRoutes()
  }

  class QueryContact {
    + number
    + page
    + limit
    + rows
  }

  class OnWhatsAppDto {
    + string jid
    + boolean isBusiness
    + string number
    + string pushName
    + string type
  }

  class JidUtils {
    + createJid(number)
    + isJidGroup(jid)
    + isJidNewsletter(jid)
  }

  %% Relationships
  ChatRouter --> ChatController : uses
  ChatController --> WAMonitor : uses
  WAMonitor --> WAInstance : waInstances
  WAInstance <|-- BaileysStartupService
  BaileysStartupService --> PrismaRepository : uses
  BaileysStartupService --> JidUtils : uses
  BaileysStartupService --> OnWhatsAppDto : creates
  ChatController --> QueryContact : parameter
Loading

File-Level Changes

Change Details Files
Recognize @newsletter JIDs when resolving numbers and when building onWhatsApp results so newsletters are treated as valid contacts of a dedicated type.
  • Extend createJid to treat numbers already containing @newsletter as full JIDs and return them unchanged
  • During onWhatsApp resolution, detect newsletter JIDs and push an OnWhatsAppDto marked as valid with type set to newsletter instead of treating them as user/group/broadcast
src/utils/createJid.ts
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
Adjust message mapping so self-sent newsletter messages receive a proper delivered status instead of default handling.
  • When normalizing Baileys messages, if the remoteJid is a newsletter and the message is fromMe, force the mapped message status to the delivered enum value
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
Introduce a fetchChannels flow that discovers newsletter channels from stored messages and exposes them via a new HTTP endpoint.
  • Add BaileysStartupService.fetchChannels to read all messages for an instance, filter newsletter remoteJids, build a de-duplicated list with lastMessageTimestamp and pushName undefined, and paginate in-memory
  • Expose fetchChannels on ChatController, delegating to the instance’s fetchChannels implementation
  • Register a POST /findChannels route in chat.router that validates the request and returns the fetchChannels result with HTTP 200
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
src/api/controllers/chat.controller.ts
src/api/routes/chat.router.ts

Possibly linked issues

  • #unknown: PR implements @newsletter support for sendMessage variants, fulfilling the issue’s request to adjust sending endpoints.
  • Feature Request: Add channel controller. #1857: They match: the issue asks about sending to WhatsApp Channels, and the PR implements @newsletter sending support.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • In fetchChannels, you load all messages for an instance into memory and then paginate in application code; consider pushing the filtering and pagination into the Prisma query (e.g., filtering by newsletter JIDs and using skip/take or cursor) to avoid scalability issues on large histories.
  • Since fetchChannels only uses remoteJid and messageTimestamp, you can tighten the Prisma where clause to pre-filter on newsletter JIDs (e.g., using a pattern or dedicated field) instead of scanning all messages and checking isJidNewsletter in a loop.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `fetchChannels`, you load all messages for an instance into memory and then paginate in application code; consider pushing the filtering and pagination into the Prisma query (e.g., filtering by newsletter JIDs and using `skip`/`take` or `cursor`) to avoid scalability issues on large histories.
- Since `fetchChannels` only uses `remoteJid` and `messageTimestamp`, you can tighten the Prisma `where` clause to pre-filter on newsletter JIDs (e.g., using a pattern or dedicated field) instead of scanning all messages and checking `isJidNewsletter` in a loop.

## Individual Comments

### Comment 1
<location> `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:5140-5142` </location>
<code_context>
     };
   }
+  public async fetchChannels(query: Query<Contact>) {
+    const page = Number((query as any)?.page ?? 1);
+    const limit = Number((query as any)?.limit ?? (query as any)?.rows ?? 50);
+    const skip = (page - 1) * limit;
+
+    const messages = await this.prismaRepository.message.findMany({
</code_context>

<issue_to_address>
**issue (bug_risk):** Handle invalid/non-numeric `page` and `limit` values to avoid NaN-based pagination.

`Number(...)` can yield `NaN` for non-numeric input (e.g. `'abc'`), which then propagates into `skip`/`slice`. Please validate `page` and `limit` (e.g. with `Number.isFinite`) and default or clamp them to sane minimum values (like 1 / 50) so pagination stays predictable.
</issue_to_address>

### Comment 2
<location> `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:4716-4717` </location>
<code_context>
       }
     }

+    if (isJidNewsletter(message.key.remoteJid) && message.key.fromMe) {
+      messageRaw.status = status[3]; // DELIVERED MESSAGE TO NEWSLETTER CHANNEL
+    }
+
</code_context>

<issue_to_address>
**suggestion:** Avoid magic index `status[3]` by using a named constant or enum-like mapping.

Indexing into `status` with `3` obscures the meaning of this assignment and tightly couples it to the array’s ordering. Prefer a named constant or enum-like value (e.g., `Status.DELIVERED`) so the intent is clear and resilient to changes in `status`.

Suggested implementation:

```typescript
    if (isJidNewsletter(message.key.remoteJid) && message.key.fromMe) {
      messageRaw.status = MESSAGE_STATUS.DELIVERED_TO_NEWSLETTER;
    }

```

You should also introduce an enum-like mapping for the message statuses near where `status` is defined (or imported), for example:

```ts
// Example – place this close to the `status` definition/import
const MESSAGE_STATUS = {
  DELIVERED_TO_NEWSLETTER: status[3],
  // optionally map other indices if they are used elsewhere, e.g.:
  // PENDING: status[0],
  // SENT: status[1],
  // READ: status[2],
} as const;
```

If your codebase already has a status enum or constant (e.g. `Status.DELIVERED`), prefer wiring this condition to that existing type instead of introducing `MESSAGE_STATUS`. In that case, replace the usage with `Status.DELIVERED` (or the correct equivalent) and remove the new mapping.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@DavidsonGomes DavidsonGomes changed the base branch from main to develop December 10, 2025 16:57
@DavidsonGomes DavidsonGomes merged commit 604c9f9 into EvolutionAPI:develop Dec 11, 2025
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants