Sendable Docs
Resources

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:

AI Markdown to WhatsApp Tool

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 UTC

Output

*Release update*

*Status:* Ready

- API healthy
- Workers healthy

> Deploy at 09:00 UTC

API 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()

On this page