Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/whatsapp-outbound-files.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@chat-adapter/whatsapp": minor
---

Implement outbound file and attachment sending for the WhatsApp adapter.

Supports binary `FileUpload` uploads, typed `Attachment` payloads (binary or HTTPS link passthrough), multi-file sequential sends, smart MIME-to-message-type mapping, caption placement with audio/long-text fallbacks, and card+file sequencing.
24 changes: 24 additions & 0 deletions apps/docs/content/adapters/official/whatsapp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,30 @@ Example: `whatsapp:1234567890:15551234567`.

Outgoing messages longer than 4096 characters are automatically chunked.

### File uploads

`postMessage` accepts both `files` and `attachments` (typed media with optional `data`, `fetchData`, or a public URL). See the [file uploads guide](/docs/files) for the shared API.

WhatsApp-specific behavior:

- **One media per message** — multiple `files` or `attachments` in a single `post()` are sent as sequential messages (the last message ID is returned).
- **Captions** — markdown (or card fallback text) is attached as a caption on the first media message when supported (max 1024 characters). Text is sent as a separate message first when the caption is too long or when the first media is audio (audio does not support captions).
- **Binary vs link** — buffers are uploaded via the Cloud API `/media` endpoint; `attachments` with only an `url` use HTTPS link passthrough (no upload). URLs must use `https://`.
- **Cards + files** — media is sent first (caption from card fallback text), then an interactive button message when the card has valid reply buttons. To send a photo with buttons, pass the image via `files` or `attachments` — card-embedded images (`imageUrl` or `<Image>` children) are not sent as native media.

```typescript title="lib/bot.ts" lineNumbers
await thread.post({
markdown: "Here's the report:",
files: [
{
data: reportBuffer,
filename: "report.pdf",
mimeType: "application/pdf",
},
],
});
```

## Feature support

<FeatureSupport />
29 changes: 24 additions & 5 deletions packages/adapter-whatsapp/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,30 @@ is closed.

## File uploads

`postMessage` accepts `FileUpload`. The adapter:

1. Calls `POST /{phoneNumberId}/media` with the binary content to
obtain a `media_id`.
2. References the `media_id` in the outbound message.
`postMessage` accepts both `files` (`FileUpload[]`) and `attachments`
(`Attachment[]`). Binary payloads upload via
`POST /{phoneNumberId}/media` to obtain a `media_id`; URL-only
attachments use WhatsApp link passthrough (HTTPS required, no upload).

**One media object per API message.** Multiple files or attachments
in a single `post()` call are sent as sequential messages. The last
message ID is returned (same convention as long-text chunking).

**Captions.** Markdown or card fallback text is attached as a caption
on the first media message when possible (max 1024 characters). A
separate leading text message is sent when the caption is too long,
or when the first media is `audio` (audio messages do not support
captions).

**MIME mapping.** `image/jpeg` and `image/png` map to `image`;
other `image/*` types (e.g. GIF) map to `document`. `video/mp4` and
`video/3gpp` map to `video`; `audio/*` maps to `audio`; everything
else maps to `document`. Pre-flight size checks throw
`ValidationError` when binary size is known (image 5 MB, audio/video
16 MB, document 100 MB).

**Cards + files.** Media is sent first (caption from card fallback
text), then an interactive card message when the card has buttons.

Media IDs expire after 30 days. For inbound media, the adapter
exposes a lazy `fetchData()` that downloads the binary on demand.
Expand Down
Loading