Broadcast a WhatsApp template to many people
send_whatsapp_broadcastSend an approved WhatsApp template to many recipients in a paced, queued broadcast; validate with dry-run, confirm, and track per-recipient outcomes via job ID.
Instructions
Send ONE approved WhatsApp template to MANY recipients — a customer list, a segment, a launch announcement. TEMPLATES ONLY: WhatsApp accepts free-form text only inside the 24-hour customer-service window that opens when THAT person messages the business, so a free-form broadcast is a separate bet on a separate window per person and Meta refuses each closed one. META HAS NO BULK ENDPOINT, so this is a paced fan-out — one message per recipient, about 10 a second — which is why it runs as a QUEUED JOB and returns a job id rather than a result: nothing has been sent when the call returns. Poll it by calling this tool again with jobId (or use get_job), and read the PER-RECIPIENT outcome; a partial failure is reported as one, never as success. PER-RECIPIENT VARIABLES ARE THE POINT: pass recipients as objects { to, components } so each person gets their own {{1}}, {{2}} values in a single broadcast. Duplicates are removed (the same number twice is a second charge for one person and trips Meta’s 6-second per-recipient limit) and unusable numbers are REFUSED BY NAME rather than silently dropped — pass skipInvalid:true to send to the rest, and they are still named in the result. RUN IT WITH dryRun:true FIRST: that costs nothing, sends nothing, and reports the exact recipient count, the template’s real category, what Meta bills for it, and whether the list fits inside the business portfolio’s moving 24-hour messaging limit. ⚠ META DOES NOT DELIVER MARKETING MESSAGES TO WHATSAPP USERS IN THE UNITED STATES AT ALL (error 131049, platform-wide since 1 April 2025) — the sends are still ACCEPTED and given message ids, so a US marketing broadcast looks like a success and is not one; the dry run says how many recipients are on +1. META BILLS THE BUSINESS, not Hermoso credits: per message delivered, at Meta’s own per-country rates, marketing always charged and utility/authentication free inside an open service window. WhatsApp policy REQUIRES the recipient’s opt-in first and nothing in the API enforces it — Meta punishes it afterwards through quality rating. Confirm-gated in code: show the user the message, the template and the recipient count, get an explicit yes, then call again with confirm:true AND confirmCount set to the exact number of distinct recipients. ACCEPTED IS NOT DELIVERED — never report a broadcast as delivered. 0 Hermoso credits.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| jobId | No | READ MODE — pass a broadcast’s job id (and nothing else) to get its status and per-recipient outcome instead of starting a new one | |
| dryRun | No | cost nothing, send nothing, queue nothing: report exactly who would be messaged, what Meta charges for it, and whether it fits the 24-hour messaging limit | |
| wabaId | No | the account the template is read from. Optional — it defaults to the account that owns phoneNumberId, and one naming a DIFFERENT account is refused | |
| confirm | No | REQUIRED true — this sends real messages to real phones and Meta bills the business for every one | |
| language | No | which language of that template, e.g. en_US. Omitted, Hermoso uses the first one it finds and reports which. | |
| template | No | an APPROVED template name. Required to SEND — a broadcast cannot be free-form text; omit only when reading a broadcast back with jobId | |
| components | No | template values SHARED by every recipient, in Meta’s component form. A per-recipient `components` overrides it. The count must match the template’s {{1}}, {{2}} placeholders — Hermoso checks that against the real template and refuses a provable mismatch before anything is dispatched | |
| recipients | No | the list: either phone numbers in full international form ["+14165550142", …], or objects { to, components } when each person gets different template values. Duplicates are removed and counted; invalid numbers are refused by name unless skipInvalid is true | |
| skipInvalid | No | drop unusable numbers instead of refusing the whole list. They are still named in the result — nothing is ever silently dropped | |
| confirmCount | No | REQUIRED — the exact number of DISTINCT valid recipients, echoed back. confirm:true alone cannot tell a 12-person test from a 2,000-person send | |
| phoneNumberId | No | WHICH NUMBER IT SENDS FROM — from list_whatsapp_accounts (a WABA can have several). Required to SEND; omit only when reading a broadcast back with jobId |