AI Markdown to WhatsApp
Convert common AI-generated markdown into WhatsApp-friendly text or table images for the API, SDK, or manual workflows.
Overview
AI tools often return markdown with headings, bullet lists, code blocks, quotes, inline emphasis, and tables. WhatsApp does not support markdown directly, so this formatter converts common AI markdown into plain text that uses WhatsApp's text formatting rules instead. When a markdown table is detected, the public API and tool return an image result so the table stays readable.
Use the public tool here:
Supported v1 Markdown
- Headings
- Bold and italic
- Strikethrough
- Inline code and fenced code blocks
- Blockquotes
- Bulleted lists and numbered lists
- Simple markdown links
- Markdown tables as SVG image output
Unsupported or complex markdown degrades to readable plain text.
Example
Input
# Release update
**Status:** Ready
- API healthy
- Workers healthy
> Deploy at 09:00 UTCOutput
*Release update*
*Status:* Ready
- API healthy
- Workers healthy
> Deploy at 09:00 UTCAPI Usage
curl -X POST "https://api.sendable.dev/v1/tools/markdown-to-whatsapp" \
-H "Content-Type: application/json" \
-d '{
"markdown": "# Release update\n\n**Status:** Ready\n\n- API healthy",
"hideLinks": true,
"splitTables": true,
"maxTableRowsPerImage": 8,
"includeTablePlaceholders": true
}'Text response:
{
"kind": "text",
"text": "*Release update*\n\n*Status:* Ready\n\n- API healthy"
}Set hideLinks to true if you want markdown links like [shokz.com](https://example.com) to disappear entirely from the output.
If the markdown contains only a table, the API returns image output:
{
"kind": "image",
"image": {
"base64": "PHN2Zy4uLg==",
"mimeType": "image/svg+xml",
"width": 976,
"height": 412
}
}If splitTables is enabled and the table is large enough, the API keeps image for compatibility and also adds images with one SVG per chunk:
{
"kind": "image",
"image": {
"base64": "PHN2Zy4uLg==",
"mimeType": "image/svg+xml",
"width": 976,
"height": 412
},
"images": [
{
"base64": "PHN2Zy4uLg==",
"mimeType": "image/svg+xml",
"width": 976,
"height": 412
},
{
"base64": "PHN2Zy4uLg==",
"mimeType": "image/svg+xml",
"width": 976,
"height": 389
}
]
}If the markdown contains both regular content and a table, the API keeps the non-table content in text and returns a table-only image:
{
"kind": "mixed",
"text": "*Inventory*\n\nReady to ship",
"image": {
"base64": "PHN2Zy4uLg==",
"mimeType": "image/svg+xml",
"width": 976,
"height": 412
}
}Set includeTablePlaceholders to true if you want removed tables to leave markers such as Table 1 in the text output.
SDK Usage
import {
Sendable,
markdownToWhatsAppResult,
markdownToWhatsAppText,
} from '@sendable-dev/sdk'
const sendable = new Sendable({
apiKey: process.env.SENDABLE_API_KEY!,
apiKeyScope: 'session',
})
const markdown = `
# Release update
**Status:** Ready
- API healthy
- Workers healthy
`
await sendable.messages.send({
chatId: '[email protected]',
text: {
content: markdownToWhatsAppText(markdown, {
hideLinks: true,
}),
},
})
const tableResult = markdownToWhatsAppResult(`
| SKU | Qty |
| --- | --- |
| A-1 | 12 |
`)
if (tableResult.kind === 'image') {
console.log(tableResult.image.mimeType) // image/svg+xml
}
const mixedResult = markdownToWhatsAppResult(`
# Inventory
Ready to ship
| SKU | Qty |
| --- | --- |
| A-1 | 12 |
`)
if (mixedResult.kind === 'mixed') {
console.log(mixedResult.text)
}
const placeholderResult = markdownToWhatsAppResult(`
# Inventory
Ready to ship
| SKU | Qty |
| --- | --- |
| A-1 | 12 |
`, {
includeTablePlaceholders: true,
})
if (placeholderResult.kind === 'mixed') {
console.log(placeholderResult.text) // includes "Table 1"
}
const splitResult = markdownToWhatsAppResult(`
| SKU | Qty |
| --- | --- |
| A-1 | 12 |
| B-9 | 4 |
| C-3 | 9 |
`, {
splitTables: true,
maxTableRowsPerImage: 2,
})
if (splitResult.kind === 'image') {
console.log(splitResult.images?.length)
}Manual Composition
If you want to build messages directly instead of converting markdown, use the SDK formatter builder:
import { waText } from '@sendable-dev/sdk'
const content = waText()
.bold('Release update')
.newline()
.bulletedList(['API healthy', 'Workers healthy'])
.build()