Sending

Text, templates, media, broadcasts

One send_message tool covers text, templates and rich media to a single recipient; send_broadcast fans the same content out to an audience. Policy decides — a blocked send explains itself instead of throwing.

Text messages

Target a stored contact by contact_id, or address a channel directly with { channel, address } (direct addressing supports whatsapp, sms, mail, telegram and discord). When a contact has identifiers on several channels, the optional top-level channel field picks the preferred one.

send_message — text
// send_message — free-form text inside an open session window
{
  "to": { "contact_id": "c_123" },
  "content": { "text": "Your order shipped this morning." }
}

// or address a channel directly
{
  "to": { "channel": "whatsapp", "address": "+33612345678" },
  "content": { "text": "Your order shipped this morning." }
}

Templates

Templates are pre-approved message formats (Meta-approved on WhatsApp) that can be sent outside session windows. content.text and content.templateName are mutually exclusive; locale selects the template language (e.g. fr, ar-MA).

send_message — template
// outside the session window: use a pre-approved template
{
  "to": { "contact_id": "c_123" },
  "content": { "templateName": "order_shipped_v2", "locale": "fr" }
}
  • list_templates returns the available templates per locale with their approval status — only APPROVED templates go through; PENDING / REJECTED ones are blocked at send time.

Media

Attach images, video, audio or documents via content.media. Every media item must carry either a url or a providerMediaId — an item with neither is rejected at validation.

FieldNotes
typeimage · video · audio · document
urlPublicly reachable HTTPS URL — the provider downloads it server-side.
providerMediaIdId of a file already uploaded to the provider (e.g. a WhatsApp media id from upload_media). Exclusive with url.
captionShown under images, videos and documents.
filenameDisplay name — mainly for documents.
mimeTypeE.g. video/mp4; inferred from the URL when omitted.
media by url
// media by public HTTPS url — the provider downloads it server-side
{
  "to": { "channel": "whatsapp", "address": "+33612345678" },
  "content": {
    "media": [
      {
        "type": "video",
        "url": "https://cdn.example.com/demo.mp4",
        "caption": "Here's the walkthrough",
        "mimeType": "video/mp4"
      }
    ]
  }
}

Local files: upload_media

When the file isn't publicly hosted (local bytes), upload it first with upload_media — pass url (fetched server-side) or data (base64) — then reference the returned media_id as providerMediaId:

upload then send
// 1. upload local bytes (base64) to the channel's provider
POST /tools/upload_media
{
  "channel": "whatsapp",
  "type": "document",
  "data": "<base64>",
  "filename": "invoice-2026-041.pdf",
  "mimeType": "application/pdf"
}
// → { "media_id": "mid.abc123…" }

// 2. reference it in send_message
POST /tools/send_message
{
  "to": { "contact_id": "c_123" },
  "content": {
    "media": [
      { "type": "document", "providerMediaId": "mid.abc123…", "filename": "invoice-2026-041.pdf" }
    ]
  }
}

Broadcasts

send_broadcast sends one piece of content to every member of an audience. Always run preview_segment first — it returns the match count and a sample so an oversized or empty segment gets caught before anything goes out.

preview → broadcast → track
// 1. preview the segment first — always
POST /tools/preview_segment   → { "count": 412, "sample": [ … ] }

// 2. send to the audience
POST /tools/send_broadcast
{
  "audience_id": "aud_black-friday-optins",
  "content": { "templateName": "bf_teaser", "locale": "fr" }
}
// → { "broadcast_id": "b_…", results per recipient: sent / blocked / failed }

// 3. track progress
POST /tools/check_delivery
{ "broadcast_id": "b_…" }
// → aggregate counts: sent, delivered, read, failed
  • Per-recipient results come back as sent / blocked / failed; each blocked entry carries its own blocked_reason and suggestion.
  • Free-form text or media in a broadcast requires an open session window per recipient — most cold audiences will block in bulk. Prefer templateName.
  • REST equivalents exist for audience management: GET /audiences, POST /audiences, POST /audiences/preview and POST /broadcasts.

Draft → approve (email & review flows)

For flows where a human (or supervising agent) reviews outbound content before it ships, the messaging_* family splits composing from sending:

two-step flow
messaging_draft    → creates a pending draft, returns draft_id + preview
messaging_preview  → renders it (e.g. markdown→HTML for email) without sending
messaging_approve  → { "draft_id": "…", "confirm": true }   ← the ONLY call that sends
messaging_list     → inspect pending/approved/sent drafts
  • messaging_approve requires confirm: true — it is the only path in this family that actually sends.
  • Drafts live in memory and are ephemeral — they do not survive a process restart.
  • Inbound email attachments are fetched lazily with messaging_attachment_fetch (by message_id + attachment_id) — polling only captures metadata, never the bytes.

Every send is journaled — follow up with check_delivery, get_stats, get_spend or get_ttr, all described in MCP tools.