Messages
The message, conversation, conversations, rcs, instagram,
calling, call and contact commands together mirror the
/messages page. From the terminal you can:
- Send WhatsApp text (with reply context), media (with voice-note flag), templates, location pins, reaction emojis, contact cards, and typing indicators.
- Send free-form RCS messages and RCS templates.
- Send Instagram DMs (text + image / video / audio media), and check the 24h / 7d reply window.
- Ask a contact for call permission (interactive text or template), then initiate the call from the backend.
- Drive conversation lifecycle: list with channel/search filters, show, resolve, reopen, assign / unassign.
- Edit contact notes, opt-out flag, tags, and the rest of the contact sidebar surface.
Everything below maps 1:1 to a button or composer action on the
/messages page. Backed by /api/v1/app/messages/*,
/api/v1/app/rcs/messages/*, /api/v1/app/instagram/*,
/api/v1/app/calling/* and /api/v1/app/contacts/*.
Quick start
# WhatsApp — send a text, then react and reply.
splashify message send --to "+919876543210" --text "Order shipped!"
splashify message reaction --to "+919876543210" --message-id <wa_id> --emoji "👍"
splashify message send --to "+919876543210" --text "Tracking link incoming" \
--context-message-id <wa_id>
# RCS — free-form send
splashify rcs send --to "+919876543210" --text "Hi from the CLI 👋"
# Instagram DM
splashify instagram dm --conversation <conv_id> --message "Thanks for the comment!"
# Ask for call permission, then dial
splashify calling permission-status --phone "+919876543210"
splashify calling send permission --to "+919876543210" \
--body-text "Can we call you about your order?"
splashify call initiate --to "+919876543210"
# Lifecycle
splashify conversations --channel whatsapp --search "Acme" --limit 50
splashify conversation <conv_id> assign --to <member_id>
splashify conversation <conv_id> resolveWhatsApp messages
splashify message send — text (with optional reply)
splashify message send --to "+919876543210" --text "Hello!"
# Reply-quote an existing message
splashify message send --to "+919876543210" --text "Same here." \
--context-message-id wamid.ABC123| Backed by | POST /api/v1/app/messages/send-text |
|---|
--context-message-id accepts the recipient’s wa_message_id and
renders as the quoted bubble above your reply — same as tapping
Reply on the page.
splashify message media — image / video / audio / document
splashify message media --to "+919876543210" \
--type image --url https://cdn.example.com/poster.jpg \
--caption "Doors open at 7"
# Audio as a voice note (single-tap play, no scrubber)
splashify message media --to "+919876543210" \
--type audio --url https://cdn.example.com/note.ogg \
--voice true
# Document with a filename hint
splashify message media --to "+919876543210" \
--type document --url https://cdn.example.com/inv.pdf \
--filename "INV-1024.pdf"| Backed by | POST /api/v1/app/messages/send-media |
|---|
splashify message template — approved template
splashify message template --to "+919876543210" \
--name order_shipped \
--lang en \
--vars '["Jane","ORD-1024","2026-05-25"]'| Backed by | POST /api/v1/app/messages/send-template |
|---|
For full Meta-shape control (header components, buttons), pass the
already-assembled template with --template '{…}'.
splashify message location — drop a pin
splashify message location --to "+919876543210" \
--lat 12.9716 --lng 77.5946 \
--name "Acme HQ" \
--address "5th Cross, Bengaluru 560001"| Backed by | POST /api/v1/app/messages/send-location |
|---|
splashify message reaction — emoji react
splashify message reaction --to "+919876543210" \
--message-id wamid.ABC123 --emoji "👍"
# Clear a reaction (pass an empty string)
splashify message reaction --to "+919876543210" \
--message-id wamid.ABC123 --emoji ""| Backed by | POST /api/v1/app/messages/send-reaction |
|---|
splashify message contact — contact card
splashify message contact --to "+919876543210" \
--contacts '[{
"name": {"formatted_name":"Acme Support","first_name":"Acme"},
"phones": [{"phone":"+918000000000","type":"WORK","wa_id":"918000000000"}],
"emails": [{"email":"hello@acme.example","type":"WORK"}]
}]'
# Or load from a file
splashify message contact --to "+919876543210" --file ./contact.json| Backed by | POST /api/v1/app/messages/send-contact |
|---|
Accepts Meta’s canonical contacts[] shape verbatim. The CLI also
accepts a single contact object (without the array wrapper) and wraps it
for you.
splashify message typing — typing-indicator bubble
splashify message typing --to "+919876543210"| Backed by | POST /api/v1/app/messages/typing-indicator |
|---|
Free to send; the bubble auto-expires server-side after ~25s. Debounce on the caller’s side if you’re driving it from a script.
RCS messages
splashify rcs send — free-form RCS text
splashify rcs send --to "+919876543210" --text "Hi 👋"| Backed by | POST /api/v1/app/rcs/messages/send-text |
|---|
Runs the standard subscription + wallet preflight (skip with --yes).
RCS pricing is separate from WhatsApp — see
splashify expenses for the per-message billing log.
splashify rcs send-template — RCS template
splashify rcs send-template --to "+919876543210" \
--template-id 7a32bce6-…| Backed by | POST /api/v1/app/rcs/messages/send-template |
|---|
The template must be APPROVED — use
splashify rcs templates to list approved templates
and splashify rcs template <id> check-status to refresh the approval
state.
Instagram DMs
The full surface lives in
splashify instagram. The two commands
that map directly to the /messages page’s Instagram tab:
# Text + optional media in the same call (renders as two bubbles)
splashify instagram dm --conversation <conv_id> --message "Thanks!" \
--media-url https://cdn.example.com/photo.jpg --media-type image
# Check the 24h / 7d reply window
splashify instagram window --conversation <conv_id>| Backed by | POST /api/v1/app/instagram/messages |
|---|---|
GET /api/v1/app/instagram/messages/window |
The window response carries state: "open" | "human_agent_only" | "expired" and seconds_remaining so you can gate further sends the
same way the page does.
Call permission + initiate
The composer’s call icon mirrors the same three-state flow as /messages:
- Check the customer’s existing call permission.
- If absent and the 24h WhatsApp window is open, send a free-form interactive request.
- Otherwise, send a permission template (which works at any time).
splashify calling permission-status — check permission
splashify calling permission-status --phone "+919876543210"| Backed by | GET /api/v1/app/calling/permission-status?phone=… |
|---|
Returns {success, is_window_open, permission_status:{status:…}}.
Status values: granted, temporary, approved, active, permanent
all mean “OK to call”. Anything else means you must ask first.
splashify calling send permission — ask for permission
# Interactive (24h window must be open)
splashify calling send permission --to "+919876543210" \
--body-text "May we call you about your order ETA?"
# Template (works any time, costs a template charge)
splashify calling send permission --to "+919876543210" \
--type template \
--template '{"name":"call_perm_v1","language":{"code":"en"}}'| Backed by | POST /api/v1/app/calling/send-permission |
|---|
Or use the dedicated template-only path:
splashify calling send permission-template --to "+919876543210" \
--name call_perm_v1 --language ensplashify call initiate — backend-side dial
splashify call initiate --to "+919876543210"| Backed by | POST /api/v1/app/calling/initiate |
|---|
The CLI does not drive WebRTC peer audio — that flow needs SDP/ICE
exchange a shell can’t provide. call initiate triggers the backend’s
WhatsApp Calling pipeline (the same one the WebRTCCallDialog ends up
routing to), and the call shows up in splashify calling calls once
Meta confirms.
Conversation lifecycle
splashify conversations — list
splashify conversations # default (open WA)
splashify conversations --status resolved # closed inbox
splashify conversations --channel rcs # RCS tab
splashify conversations --channel instagram --search "Acme"
splashify conversations --page 2 --limit 50| Backed by | GET /api/v1/app/messages/conversations?… |
|---|
--channel mirrors the inbox switcher on the page (whatsapp / rcs /
instagram). --page-size is kept as an alias of --limit for backward
compatibility with older scripts.
splashify conversation <id> — show one
splashify conversation 7a32bce6-…| Backed by | GET /api/v1/app/messages/conversations/:id |
|---|
Returns the conversation row plus the message thread.
Lifecycle actions
splashify conversation <id> resolve # close the conversation
splashify conversation <id> reopen # flip back to open
splashify conversation <id> assign --to <member_id> # assign to a teammate
splashify conversation <id> assign --to "" # unassign| Backed by | POST /app/messages/conversations/:id/resolve |
|---|---|
POST /app/messages/conversations/:id/reopen | |
POST /app/messages/conversations/:id/assign |
Contact mutations
splashify contact update — sparse PUT
# Save a note (page: handleSaveNotes)
splashify contact update <id> --notes "VIP. Prefers WhatsApp over email."
# Marketing opt-out toggle (page: handleOptToggle)
splashify contact update <id> --opted-out true
# Update name + email + website (page: contact sidebar)
splashify contact update <id> \
--name "Jane Doe" --email jane@acme.example --website https://acme.example
# Replace the tag set in one call
splashify contact update <id> --tags vip,lead,trial
# Bonus: raw JSON for fields not covered by named flags
splashify contact update <id> --data '{"column_data":"{\"company\":\"Acme\"}"}'| Backed by | PUT /api/v1/app/contacts/:id |
|---|
Sparse — only the flags you explicitly pass are included in the body.
--data JSON overlays last and wins over the named flags when they
conflict.
splashify contact tag / untag — add and remove tags
splashify contact tag <id> --tags vip,lead
splashify contact untag <id> --tags lead| Backed by | POST /api/v1/app/contacts/:id/tags |
|---|---|
DELETE /api/v1/app/contacts/:id/tags?tags=… |
splashify contact block / unblock
splashify contact block <id>
splashify contact unblock <id>| Backed by | POST /api/v1/app/contacts/:id/block |
|---|---|
POST /api/v1/app/contacts/:id/unblock |
Blocking stops every outbound channel for that contact and refuses
inbound messages too — same behaviour as the /messages block confirm
dialog.
Common workflows
Reply to the latest inbound on every open WhatsApp conversation
splashify conversations --channel whatsapp --status open --limit 50 | \
jq -r '.conversations[] | "\(.conversation_id) \(.phone_number)"' | \
while read CID PHONE; do
splashify message send --to "$PHONE" --text "Thanks for reaching out — we'll be right with you."
doneResolve every conversation older than 7 days
CUTOFF=$(date -u -v-7d '+%Y-%m-%dT%H:%M:%SZ') # macOS
# CUTOFF=$(date -u -d '7 days ago' '+%Y-%m-%dT%H:%M:%SZ') # linux
splashify conversations --status open --limit 200 | \
jq -r --arg c "$CUTOFF" '.conversations[] | select(.last_message_at < $c) | .conversation_id' | \
xargs -I{} splashify conversation {} resolveTriage unassigned conversations to the on-call agent
AGENT_ID=$(splashify team | jq -r '.members[] | select(.role=="oncall") | .member_id')
splashify conversations --status open --limit 200 | \
jq -r '.conversations[] | select(.assigned_to == null or .assigned_to == "") | .conversation_id' | \
xargs -I{} splashify conversation {} assign --to "$AGENT_ID"Run a “request call permission → dial” loop
PHONE="+919876543210"
STATUS=$(splashify calling permission-status --phone "$PHONE" | jq -r '.permission_status.status // "none"')
case "$STATUS" in
granted|temporary|approved|active|permanent)
splashify call initiate --to "$PHONE"
;;
*)
splashify calling send permission --to "$PHONE" \
--body-text "May we call you about your recent order?"
echo "Permission requested — try again once the customer approves."
;;
esacTroubleshooting
message reaction returns “message not found” — --message-id
must be the recipient’s wa_message_id (a wamid.… string), not the
internal message_id UUID. List recent messages with
splashify conversation <id> and grab the wa_message_id from the
thread.
message contact errors with “invalid contacts” — the array must
match Meta’s exact shape. name.formatted_name is required; phones[]
needs at least one entry with phone (and ideally wa_id so it links
to a WhatsApp profile on the recipient’s side).
rcs send returns insufficient_balance — RCS pricing is separate
from WhatsApp. Run splashify wallet and recharge from the app. The
preflight uses a coarse balance threshold; the actual per-message cost
is debited after delivery.
instagram dm returns “outside 7d window” — Meta blocks outbound
DMs after the customer hasn’t messaged in 7 days. Use
splashify instagram window --conversation <id> to see exactly when
the window expires.
call initiate succeeds but no call rings — backend-side dial uses
the WhatsApp Calling pipeline; the customer must have granted call
permission first (calling permission-status should show
granted/temporary/approved/active/permanent). If not,
calling send permission and try again after approval.
See also
splashify templates— WhatsApp + RCS template CRUD; the source of--templatenames and--template-idUUIDs.splashify canned-messages— composer canned message shortcuts.splashify instagram-automation— IG account connect + comment-to-DM rules.splashify calling— full calling surface including templates, analytics, history.splashify expenses— per-message billing log.