messages

Send message

The one outbound door for a human, the AI SDR or an external agent: reply into a thread (conversation_sid) or start one (new_thread), exactly one of the two, else 422. channel picks the arm and kind must belong to it; a first email needs subject. A counterpart resolve that cannot ask gtm-api or playtables refuses the send whole (429 gtm_busy, 403 gtm_access_lost or gtm_scope_missing, 503 gtm_unavailable): try again later.

Guards: an agent-authored send whose reason is gone is refused first (409 message_superseded with context.cause: end the turn, a newer plan writes its own); a billing hold is 402; a live sender profile must hold an active account for the channel, else 409 sender_unavailable (a channel the thread has no account for is 422 on channel); an agent send is refused on a do_not_contact thread (409 stoplisted), past the per-thread auto-message cap (429 limit_exceeded) and where the agent is switched off (409 agent_disabled_on_thread). Mode gate: an agent-authored send on a copilot thread is stored as a pending_approval draft, a SUCCESS with result.gate = "pending_approval" (do not retry: a person approves it). A user-authored send always queues. The author is the JWT's, never the body's.

The answer is an accepted row, not a receipt: dispatch runs later (scheduled_at, the sender profile's pace) and re-checks the sender, a reply landed meanwhile and the stoplist. result.conversation is the thread, freshly created on the new_thread arm.

Contract:

  • MCP tool send_message, registry package mcp.unibox/messages, mount unibox.inbox.
  • Operation action, response envelope action.
  • Flags: dangerous: the MCP layer gates it behind a preview/commit token, and the effect cannot be undone through this API.
post/api/messages/send

Request

  • Base URL
    https://beta.getsales.io/unibox/v1
  • Auth
    HTTP bearer and API key in header Team-SID

Request body

conversation_sidstring nullable

Reply into this existing thread. Exactly one of conversation_sid | new_thread, else 422 invalid_input.

reply_to_message_sidstring nullable

Quote this message of the thread under the reply (ub_ms_...); a reply into conversation_sid only, never with new_thread.

channel'linkedin' | 'email' required

linkedin | email. It must be connected on the thread's sender profile, else 422 channel_not_connected.

kind'linkedin_message' | 'linkedin_connection_note' | 'linkedin_inmail' | 'email' required

Must belong to channel: linkedin takes linkedin_message | linkedin_inmail | linkedin_connection_note, email takes email. A mismatch is 422.

subjectstring nullable

Email only, max 998. REQUIRED on an email new_thread (a reply inherits the thread subject); ignored on LinkedIn.

bodystring required

The message text, min 1 char, plain text (email HTML composition is v1.5). NOTE the key: body here, body_text on update_message.

scheduled_atstring nullable

ISO 8601 UTC. Holds the row in the queue until then; null = as soon as the per-sender-profile queue allows. The final gate still runs at that moment.

agent_run_sidstring nullable

Provenance for an AGENT turn: the run that produced this text. Silently dropped for any non-agent actor. The dispatch gate and the dialog exchange meter key off it.

attach_answer_sidsstring[] nullable

An AGENT turn only: the answers the run carried whose file goes out on request and the lead asked for (ub_an_ sids); the file goes with the message. A file that goes out every time needs no name. Ignored for other actors and answers the run did not carry.

Response

action success envelope.

successtrue required
operation'action' required
actionstring required

kebab-case verb; matches the route segment.

Changes

No recorded changes to this operation across all 1 revision of this API.