Skip to Content
splashify CLIMessages (inbox)

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> resolve

WhatsApp 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 byPOST /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 byPOST /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 byPOST /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 byPOST /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 byPOST /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 byPOST /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 byPOST /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 byPOST /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 byPOST /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 byPOST /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:

  1. Check the customer’s existing call permission.
  2. If absent and the 24h WhatsApp window is open, send a free-form interactive request.
  3. Otherwise, send a permission template (which works at any time).

splashify calling permission-status — check permission

splashify calling permission-status --phone "+919876543210"
Backed byGET /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 byPOST /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 en

splashify call initiate — backend-side dial

splashify call initiate --to "+919876543210"
Backed byPOST /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 byGET /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 byGET /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 byPOST /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 byPUT /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 byPOST /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 byPOST /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." done

Resolve 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 {} resolve

Triage 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." ;; esac

Troubleshooting

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