# How to Handle Sending and Receiving Media Messages with Baileys (Including Caching)

> Master sending and receiving media messages with Baileys. Learn efficient media handling with encryption, upload, and sending steps, plus caching techniques to avoid duplicates and store received files.

- Repository: [WhiskeySockets/Baileys](https://github.com/WhiskeySockets/Baileys)
- Tags: how-to-guide
- Published: 2026-08-01

---

**Baileys processes every media message through a three-step pipeline—encrypt and prepare with `encryptedStream`, upload via `uploadMedia`, then send with `sendMessage`—while the optional `mediaCache` store prevents duplicate uploads and the `downloadContentFromMessage` helper enables local caching of received files.**

The Baileys library by WhiskeySockets is a popular WhatsApp Web API for Node.js that handles media differently than text. Understanding how to send and receive media messages with Baileys—including its built-in caching mechanisms—is essential for building performant bots and integrations. This guide walks through the complete media pipeline using actual source code from the repository.

## The Media Message Pipeline in Baileys

Baileys implements a unified pipeline for all media types—images, videos, audio, documents, and stickers. The flow is consistent regardless of format, with specialized handling in [`src/Utils/messages-media.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Utils/messages-media.ts).

### Step 1: Encrypt and Prepare the Media

When you call `sendMessage` with media, Baileys first encrypts the raw bytes. In [[`src/Utils/messages-media.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Utils/messages-media.ts)](https://github.com/WhiskeySockets/Baileys/blob/master/src/Utils/messages-media.ts#L85-L99), the `encryptedStream` function:

- Generates a cryptographically random `mediaKey`
- Expands it using HKDF via `getMediaKeys`
- Streams the file through AES-256-CBC encryption
- Writes the encrypted output to a temporary file

```typescript
// Encrypted file is written to encFilePath for upload
const { encFilePath, ... } = await encryptedStream({
  media: buffer,
  mediaType: 'image' // or 'video', 'audio', 'document', etc.
})

```

### Step 2: Upload the Encrypted File

The encrypted file is uploaded through [`messages-media.ts:L165-L176`](https://github.com/WhiskeySockets/Baileys/blob/master/src/Utils/messages-media.ts#L165-L176) via `uploadMedia`. Baileys selects the safest upload implementation to avoid known issues:

- **Node.js environments**: Uses `uploadWithNodeHttp` to bypass the Undici memory-bloat bug
- **Other environments**: Falls back to standard Fetch

This selection logic appears at [`messages-media.ts:L162-L166`](https://github.com/WhiskeySockets/Baileys/blob/master/src/Utils/messages-media.ts#L162-L166). The upload returns either a `directPath` (preferred) or a public `url` for the encrypted blob.

### Step 3: Send the Final Message

The `sendMessage` implementation in [[`src/Socket/messages-send.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Socket/messages-send.ts)](https://github.com/WhiskeySockets/Baileys/blob/master/src/Socket/messages-send.ts) constructs a `WAGenericMediaMessage` protobuf containing:

- `mediaKey` – the encryption key needed for decryption
- `url` or `directPath` – the upload location
- `fileSha256` – hash for integrity verification
- Metadata: caption, thumbnail, mimetype, file length

This protobuf is serialized and transmitted over the WhatsApp Web WebSocket.

## Upload-Side Caching: Avoid Duplicate Uploads

Baileys provides an optional **upload-side cache** to prevent re-uploading identical files. This is particularly valuable when broadcasting the same image to multiple chats or retrying failed sends.

### How Upload Caching Works

In [`src/Utils/messages.ts:L151-L218`](https://github.com/WhiskeySockets/Baileys/blob/master/src/Utils/messages.ts#L151-L218), after a successful upload:

1. Baileys computes a `cacheableKey` from the media's SHA-256 hash and mime type (`mediaMessageSHA256B64`)
2. Stores the raw, serialized protobuf (`WAProto.Message.encode`) in your provided `mediaCache`
3. On subsequent sends with identical content, retrieves the cached protobuf and skips encryption/upload entirely

### Configuring the Upload Cache

Provide any `CacheStore` implementation when creating your socket. The interface is defined in [[`src/Types/Socket.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Types/Socket.ts)](https://github.com/WhiskeySockets/Baileys/blob/master/src/Types/Socket.ts):

```typescript
import makeWASocket, { CacheStore } from '@whiskeysockets/baileys'

// Simple in-memory Map implementation
const mediaCache: CacheStore = new Map<string, Buffer>()

const sock = makeWASocket({
  auth: state,
  mediaCache // Enable upload caching
})

```

### Complete Sending Example with Caching

```typescript
import makeWASocket, { useSingleFileAuthState } from '@whiskeysockets/baileys'
import { readFile } from 'fs/promises'

const { state } = useSingleFileAuthState('./auth_info')
const sock = makeWASocket({
  auth: state,
  mediaCache: new Map<string, Buffer>()
})

const imgBuffer = await readFile('./promotional-banner.jpg')

// First call: encrypts, uploads, and caches the protobuf
await sock.sendMessage('123456789@s.whatsapp.net', {
  image: imgBuffer,
  caption: 'Check out our sale!'
})

// Second call with identical buffer: instant send from cache
await sock.sendMessage('987654321@s.whatsapp.net', {
  image: imgBuffer,
  caption: 'Check out our sale!'
})

```

The cache key is automatically derived from `mediaMessageSHA256B64`—no manual key management required.

## Download-Side Caching: Optimize Received Media

For **receiving media messages with Baileys**, repeated downloads of the same content waste bandwidth and time. The `downloadContentFromMessage` helper in [`src/Utils/messages-media.ts:L31-L46`](https://github.com/WhiskeySockets/Baileys/blob/master/src/Utils/messages-media.ts#L31-L46) returns a stream, allowing you to implement your own caching layer.

### The Download and Decryption Flow

When processing an incoming message via [[`messages-recv.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/messages-recv.ts)](https://github.com/WhiskeySockets/Baileys/blob/master/src/Socket/messages-recv.ts):

1. Extract `mediaKey`, `directPath`, and `url` from the message protobuf
2. Call `downloadContentFromMessage` with the media type specifier
3. Baileys derives decryption keys via `getMediaKeys`, builds the download URL via `getUrlFromDirectPath`, and streams through `downloadEncryptedContent` with on-the-fly AES-256-CBC decryption

The decryption transform handles chunked range requests and IV preparation automatically ([`messages-media.ts:L31-L55`](https://github.com/WhiskeySockets/Baileys/blob/master/src/Utils/messages-media.ts#L31-L55)).

### Implementing a Download Cache

Since Baileys returns a stream rather than a buffer, you consume and cache the result:

```typescript
import { downloadContentFromMessage, extensionForMediaMessage } from '@whiskeysockets/baileys'
import LRUCache from 'lru-cache'

// LRU cache: max 100 items, 5 minute TTL
const downloadCache = new LRUCache<string, Buffer>({
  max: 100,
  ttl: 1000 * 60 * 5
})

sock.ev.on('messages.upsert', async ({ messages }) => {
  for (const msg of messages) {
    const media = msg.message?.imageMessage || 
                  msg.message?.videoMessage ||
                  msg.message?.documentMessage
    if (!media) continue

    const ext = extensionForMediaMessage(msg.message)
    const cacheKey = `${msg.key.remoteJid}:${msg.key.id}${ext}`

    let buffer = downloadCache.get(cacheKey)
    if (!buffer) {
      // Download and decrypt from WhatsApp CDN
      const stream = await downloadContentFromMessage({
        mediaKey: media.mediaKey!,
        directPath: media.directPath,
        url: media.url
      }, 'image') // or 'video', 'audio', 'document'

      buffer = Buffer.from(await new Response(stream as any).arrayBuffer())
      downloadCache.set(cacheKey, buffer)
      console.log('📥 Downloaded and cached:', cacheKey)
    } else {
      console.log('⚡ Cache hit:', cacheKey)
    }

    // Use buffer (save to disk, process, forward, etc.)
    await writeFile(`./downloads/${cacheKey}`, buffer)
  }
})

```

## Key Files and Their Roles

| File | Responsibility |
|------|---------------|
| [[`src/Utils/messages-media.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Utils/messages-media.ts)](https://github.com/WhiskeySockets/Baileys/blob/master/src/Utils/messages-media.ts) | Core encryption (`encryptedStream`), upload (`uploadMedia`), download (`downloadContentFromMessage`), and thumbnail generation |
| [[`src/Utils/messages.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Utils/messages.ts)](https://github.com/WhiskeySockets/Baileys/blob/master/src/Utils/messages.ts) | Orchestrates the full send flow and manages `mediaCache` integration |
| [[`src/Socket/messages-send.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Socket/messages-send.ts)](https://github.com/WhiskeySockets/Baileys/blob/master/src/Socket/messages-send.ts) | Constructs final `WAGenericMediaMessage` protobuf and transmits |
| [[`src/Socket/messages-recv.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Socket/messages-recv.ts)](https://github.com/WhiskeySockets/Baileys/blob/master/src/Socket/messages-recv.ts) | Parses incoming stanzas and extracts media metadata |
| [[`src/Types/Socket.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Types/Socket.ts)](https://github.com/WhiskeySockets/Baileys/blob/master/src/Types/Socket.ts) | Defines `mediaCache?: CacheStore` for socket configuration |
| [[`src/Types/Message.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Types/Message.ts)](https://github.com/WhiskeySockets/Baileys/blob/master/src/Types/Message.ts) | Per-message options including cache-related flags |

## Performance Considerations

- **Upload cache keys** are SHA-256 based—identical bytes produce identical keys across mime types
- **Download caching** is consumer-implemented because Baileys cannot know your storage constraints or retention policies
- **Memory vs. external cache**: For production, replace `Map` or `LRUCache` with Redis, S3, or a database-backed `CacheStore`
- **Range request handling**: Downloads use HTTP range requests for resumability; large files stream efficiently without full memory buffering

## Summary

- **Sending media** requires three steps: `encryptedStream` (encrypt), `uploadMedia` (upload), and `sendMessage` (transmit)—all orchestrated automatically when you pass a buffer to `sock.sendMessage()`
- **Upload-side caching** via `mediaCache` prevents duplicate encryption and upload; the cache stores raw protobufs keyed by SHA-256 in [[`messages.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/messages.ts)](https://github.com/WhiskeySockets/Baileys/blob/master/src/Utils/messages.ts#L151-L218)
- **Receiving media** uses `downloadContentFromMessage` from [`messages-media.ts:L31-L46`](https://github.com/WhiskeySockets/Baileys/blob/master/src/Utils/messages-media.ts#L31-L46) with on-the-fly AES-256-CBC decryption
- **Download-side caching** is your responsibility—consume the stream, store the buffer, and key by message ID for reuse
- All media types share the same pipeline; only the protobuf wrapper and metadata differ

## Frequently Asked Questions

### How does Baileys generate the media encryption key?

Baileys generates a random 32-byte `mediaKey` in [`encryptedStream`](https://github.com/WhiskeySockets/Baileys/blob/master/src/Utils/messages-media.ts#L85-L99), then expands it using HKDF-SHA256 via `getMediaKeys` to produce separate keys for AES-256-CBC encryption and HMAC-SHA256 authentication.

### What cache implementation should I use for production?

Any object matching the `CacheStore` interface works. For single-instance deployments, `lru-cache` with TTL is sufficient. For horizontal scaling, implement the interface with Redis, PostgreSQL, or S3-backed storage—persist `Buffer` values as base64 or binary.

### Can I disable caching for specific messages?

Yes. The `mediaCache` is socket-level and optional. To bypass cache for a single send, you can temporarily mutate the socket options or implement a `CacheStore` that ignores `set` calls based on custom logic. The per-message options in [[`src/Types/Message.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Types/Message.ts)](https://github.com/WhiskeySockets/Baileys/blob/master/src/Types/Message.ts) do not currently include cache bypass flags.

### Why does downloading return a stream instead of a buffer?

Streaming prevents memory exhaustion with large files (videos, long audio). WhatsApp media can exceed 100MB. By returning a `Transform` stream from `downloadEncryptedContent`, Baileys allows you to pipe directly to disk, process chunks, or accumulate into a cache only when needed.