# The Messaging Post Office (Sending & Receiving)

> How texts and media travel between your app and WhatsApp: sorting messages in the queue to avoid speed tickets, and ringing webhooks to deliver incoming messages instantly.

- Repository: rmyndharis/OpenWA
- GitHub: https://github.com/rmyndharis/OpenWA
- Human wiki: https://grok-wiki.com/public/wiki/rmyndharis-openwa-2c9996a09a22
- Complete Markdown: https://grok-wiki.com/public/wiki/rmyndharis-openwa-2c9996a09a22/llms-full.txt

## Source Files

- `src/modules/message/message.controller.ts`
- `src/modules/message/message.service.ts`
- `src/modules/webhook/webhook.service.ts`
- `src/modules/queue/processors/webhook.processor.ts`
- `src/modules/queue/queue.module.ts`

---

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [src/modules/message/message.controller.ts](src/modules/message/message.controller.ts)
- [src/modules/message/message.service.ts](src/modules/message/message.service.ts)
- [src/modules/message/bulk-message.service.ts](src/modules/message/bulk-message.service.ts)
- [src/modules/webhook/webhook.service.ts](src/modules/webhook/webhook.service.ts)
- [src/modules/queue/processors/webhook.processor.ts](src/modules/queue/processors/webhook.processor.ts)
- [src/modules/queue/queue.module.ts](src/modules/queue/queue.module.ts)
</details>

# The Messaging Post Office (Sending & Receiving)

At the heart of the OpenWA repository is its messaging infrastructure—conceptualized as a highly organized "Messaging Post Office." Managing communications between a modern web application and the WhatsApp network is a delicate balance. To prevent sending accounts from receiving WhatsApp speed bans (spam and speed tickets), the system must carefully queue, delay, and throttle outgoing bulk campaigns. Simultaneously, to ensure application responsiveness, incoming events and messages must be delivered instantly and reliably via webhooks without losing data during traffic spikes.

This page explains how texts, images, and other media travel between your application and WhatsApp. It details the lifecycle of direct single messages, the rate-limiting and template-interpolation mechanisms used in bulk campaigns, and the Redis-backed BullMQ event-driven webhook architecture that guarantees delivery of incoming events.

---

## Outgoing Messaging Workflow

Outgoing mail is split into two primary lanes: **Single Direct Messaging** (sent synchronously with immediate feedback) and **Bulk Messaging** (processed asynchronously with controlled pacing). Both lanes interact with a lifecycle hook manager, enabling external plugins to intercept, mutate, or block messages before they touch the WhatsApp network.

### Single Direct Messaging

When your application triggers a standard send request (e.g., text, image, video, location, or document), the API flows from the controller directly through to the active WhatsApp engine. The lifecycle follows a synchronous sequence:

1. **API Entry:** The consumer calls an operational endpoint like `/sessions/:sessionId/messages/send-text` or `/sessions/:sessionId/messages/send-image`.
2. **Pre-Send Hook Inspection:** The controller delegates to `MessageService`, which immediately triggers the `message:sending` hook. Registered plugins can examine the payload, modify the message content (e.g., stripping sensitive information), or cancel the delivery entirely.
3. **Database Pending State:** If the hooks permit execution, the service saves the outgoing message record in the database with a status of `MessageStatus.PENDING`.
4. **Engine Delivery:** The active session engine (e.g., Baileys or similar WhatsApp Web wrapper) attempts to transmit the message over the WhatsApp network.
5. **Post-Send Outcomes:** 
   - On **success**, the database record is updated to `SENT`, the real `waMessageId` returned by WhatsApp is saved, and a `message:sent` hook fires.
   - On **failure**, the record transitions to `FAILED` and a `message:failed` hook is executed, alerting plugins and system logs.

```typescript
// From src/modules/message/message.service.ts
const { continue: shouldContinue, data: hookData } = await this.hookManager.execute(
  'message:sending',
  { sessionId, input: dto, type: 'text' },
  { sessionId, source: 'MessageService' },
);

if (!shouldContinue) {
  throw new BadRequestException('Message sending blocked by plugin');
}

// Save message as pending BEFORE sending
const message = await this.saveOutgoingMessage(sessionId, {
  chatId: finalDto.chatId,
  body: finalDto.text,
  type: 'text',
});
```

Sources: [src/modules/message/message.controller.ts:41-137](src/modules/message/message.controller.ts#L41-L137), [src/modules/message/message.service.ts:25-207](src/modules/message/message.service.ts#L25-L207)

### Bulk Messaging and Rate Limiting

Sending messages to many recipients simultaneously without rate limits is the fastest way to get a phone number flagged and banned. To circumvent this, the "Post Office" includes a specialized **Bulk Messaging Engine** that handles campaigns asynchronously with deliberate delays.

- **Asynchronous Processing:** When a batch is posted, the `BulkMessageService` initializes a `MessageBatch` database entity with a progress tracker (tracking `total`, `sent`, `failed`, `pending`, and `cancelled` counts) and immediately returns an operational tracking URL to the client.
- **Variable Template Interpolation:** Message text or media captions can include variables in the format `{variableName}`. The bulk process evaluates these parameters dynamically per recipient (e.g., inserting names or order numbers) before calling the engine.
- **Pacing Control & Deliberate Delay:** The processor loops through the batch and introduces a sleep delay between successive messages. This delay is computed based on `delayBetweenMessages` (defaulting to `3000`ms) and is combined with `randomizeDelay` (which appends a random variance of 0–2 seconds). This mimics human-like behavior and avoids triggering anti-spam alerts.
- **Mid-Campaign Cancellation:** Campaigns are tracked in-memory using an active batch map. If the client calls the cancellation endpoint, the engine immediately flags the batch, marks remaining messages as `cancelled`, updates the state in the database, and halts the background processing loop.

```typescript
// From src/modules/message/bulk-message.service.ts
// Delay before next message (except for last) to avoid rate limits
if (i < batch.messages.length - 1 && this.processingBatches.get(batch.id)) {
  const delay = this.calculateDelay(batch.options);
  await this.sleep(delay);
}
```

Sources: [src/modules/message/message.controller.ts:284-364](src/modules/message/message.controller.ts#L284-L364), [src/modules/message/bulk-message.service.ts:37-86](src/modules/message/bulk-message.service.ts#L37-L86), [src/modules/message/bulk-message.service.ts:128-221](src/modules/message/bulk-message.service.ts#L128-L221)

---

## Direct vs. Bulk Messaging Comparison

| Feature | Single Messaging | Bulk Messaging |
| :--- | :--- | :--- |
| **Execution Context** | Synchronous (immediate HTTP response) | Asynchronous (processed in background) |
| **API Endpoint** | `/sessions/:sessionId/messages/send-text` | `/sessions/:sessionId/messages/send-bulk` |
| **Primary Class** | `MessageService` | `BulkMessageService` |
| **Rate Throttling** | None (instantaneous execution) | Multi-second delay + randomized human jitter |
| **Content Templating**| Standard static input payload | Dynamic `{variable}` key replacement per message |
| **Cancellation** | Not applicable (instant) | Cancellable at any time during execution |

---

## Incoming Messaging and Webhook Delivery

When an incoming text or media file is received by the WhatsApp session, the system rings configured external webhooks. To guarantee that webhook payloads are never lost and are delivered in the exact order they occurred, OpenWA uses a Redis-backed BullMQ queueing structure.

```mermaid
flowchart TD
  subgraph Client ["Client Target & API Consumer"]
    A[API Client]
    B[External Webhook Endpoint]
  end

  subgraph API ["OpenWA NestJS Instance"]
    C[MessageController]
    D[WebhookService]
    E[MessageService]
  end

  subgraph Engine ["WhatsApp Core"]
    F[WhatsApp Engine Instance]
    G[WhatsApp Network]
  end

  subgraph DB ["Data Store"]
    H[(Database - Messages & Webhooks)]
  end

  subgraph QueueServer ["Queue System (Redis & BullMQ)"]
    I[(Redis Database)]
    J[BullMQ Webhook Queue]
    K[WebhookProcessor]
  end

  %% Direct Outgoing Flow
  A -- "1. Post single message" --> C
  C -- "2. Trigger sending hooks & save PENDING" --> E
  E -- "3. Execute send" --> F
  F -- "4. Network dispatch" --> G
  F -- "5. Update to SENT & save ID" --> E
  E -- "6. Return HTTP 201" --> C

  %% Incoming Webhook Flow
  G -- "A. Deliver incoming event" --> F
  F -- "B. Save incoming message" --> E
  E -- "C. Save to DB" --> H
  F -- "D. Dispatch event" --> D
  D -- "E. Fetch active webhooks" --> H
  D -- "F. Push webhook job with headers & retry settings" --> J
  J -- "G. Cache job data" --> I
  K -- "H. Process job & fetch status" --> J
  K -- "I. POST with Signature & Retry Count" --> B
  K -- "J. On Success: Update lastTriggeredAt" --> H
```

Sources: [src/modules/queue/queue.module.ts:16-46](src/modules/queue/queue.module.ts#L16-L46)

### Webhook Dispatch and Idempotency

When the active engine generates a new event (such as `message.received`), `WebhookService` handles dispatching:

1. **Webhook Matching:** The service queries all active webhook targets registered for the session. Webhooks are selected if their event subscription array contains the specific incoming event or the wildcard `*` symbol.
2. **Idempotency Safeguard:** To prevent receiving endpoints from processing duplicate notifications, the system generates a single deterministic `idempotencyKey` based on the event details and session information. This key is identical for all registered webhooks receiving that specific event.
3. **Unique Delivery Tracking:** Each destination webhook gets a unique, freshly generated `deliveryId` for precise logs.
4. **Before-Dispatch Hook:** The `webhook:before` hook lets plugins inspect or alter the final payload structure.

Sources: [src/modules/webhook/webhook.service.ts:155-207](src/modules/webhook/webhook.service.ts#L155-L207)

### Reliable Queue Delivery (Redis & BullMQ)

If queue processing is enabled (`queue.enabled` set to `true`), direct synchronous dispatch is skipped. The webhook is added to BullMQ:

- The job is pushed into the `QUEUE_NAMES.WEBHOOK` (which resolves to `'webhook'`) queue.
- It includes the destination URL, custom headers, payload, HMAC signature, and retry settings.
- BullMQ is configured to automatically perform exponential backoffs using the configured `webhook.retryDelay` interval (defaulting to `5000`ms) to handle intermittent outages on the receiving end.

Sources: [src/modules/webhook/webhook.service.ts:209-262](src/modules/webhook/webhook.service.ts#L209-L262), [src/modules/queue/queue.module.ts:16-46](src/modules/queue/queue.module.ts#L16-L46)

### Webhook Processor and Retry Backoff

The `WebhookProcessor` acts as the worker consuming jobs from Redis. For each job, it runs a target execution sequence:

1. **Header Updates:** It sets the `X-OpenWA-Retry-Count` header to the current attempt number (`job.attemptsMade`).
2. **Payload Security Signing:** If the webhook has an associated secret key, the processor creates a digital signature of the JSON body via HMAC-SHA256 and attaches it to the `X-OpenWA-Signature` header. This allows the receiving server to verify that the payload is authentic and was sent by your OpenWA instance.
3. **HTTP Fetch Delivery:** It attempts an HTTP POST request to the destination URL with a strict 10-second timeout.
4. **Successful Delivery:** If the response returns an HTTP 2xx code, the worker updates the `lastTriggeredAt` field of the webhook configuration in the database and triggers the `webhook:delivered` plugin hook.
5. **Failure & Re-Queueing:** If the endpoint returns an error or times out, the processor catches it:
   - If the job has not reached its maximum retries, the worker logs the issue and throws an error. This signals BullMQ to schedule the job for a retry using exponential backoff.
   - If it was the final attempt, the error is logged and the `webhook:error` plugin hook is executed to handle the definitive failure.

```typescript
// From src/modules/queue/processors/webhook.processor.ts
const requestHeaders = {
  ...headers,
  'X-OpenWA-Retry-Count': String(job.attemptsMade),
};

try {
  const response = await fetch(url, {
    method: 'POST',
    headers: requestHeaders,
    body: JSON.stringify(payload),
    signal: AbortSignal.timeout(10000),
  });

  const responseTime = Date.now() - startTime;
  const success = response.ok;

  if (!success) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  }
  // ... Handle success and lastTriggeredAt updates
} catch (error) {
  // ... Handle exponential retry propagation or final webhook:error hook
  throw error;
}
```

Sources: [src/modules/queue/processors/webhook.processor.ts:30-136](src/modules/queue/processors/webhook.processor.ts#L30-L136)

### Direct Delivery Fallback

If Redis or the queue is disabled in the server configurations, `WebhookService` falls back to direct synchronous HTTP delivery. While this avoids a Redis dependency, it does not offer background queue persistence. It executes a synchronous loop with a custom backoff delay, trying to deliver the message until it succeeds or exhausts the retry configuration.

Sources: [src/modules/webhook/webhook.service.ts:264-354](src/modules/webhook/webhook.service.ts#L264-L354)

---

## Summary

The OpenWA messaging post office bridges the gap between your application logic and the real-time requirements of the WhatsApp protocol. Single messages are dispatched through a rich, plugin-extensible hook pipeline, while bulk campaigns are safely distributed with configurable random delays to protect sending numbers from WhatsApp speed violations. Incoming events are secured through cryptographic HMAC-SHA256 signatures, equipped with idempotency keys to prevent duplicates, and queued using Redis and BullMQ to guarantee that no webhook notifications are lost during peak traffic.

Sources: [src/modules/message/message.service.ts:469-475](src/modules/message/message.service.ts#L469-L475)
