Skip to content
LinqCopy agent prompt

Sending Messages

How to send text, media, and rich messages with the Linq API.

The Linq API lets you send messages containing text, media, or a mix of both across iMessage, RCS, and SMS.

There are two ways to open a conversation with a new recipient, depending on whether you want Linq to choose the sending line for you.

POST /v3/messages takes the recipients and the message and nothing else — no from. Linq resolves both the line and the chat: it reuses the chat those recipients are already in while that line can still send, opens a new chat on the best available line when there isn’t one, and moves the recipients onto a fresh line when their current one can’t send. Traffic spreads across your pool on its own, and lines you add later enter the rotation with no code change.

Terminal window
curl -X POST https://api.linqapp.com/api/partner/v3/messages \
-H "Authorization: Bearer $LINQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": [
"+14155559876"
],
"message": {
"parts": [
{
"type": "text",
"value": "Hi! Thanks for reaching out — how can we help?"
}
]
}
}'

The response says exactly what happened:

FieldWhat it tells you
fromThe line the message actually went out on
chat_id, created_new_chatWhich chat it landed in, and whether that chat is new
from_selection.reasonreused_active_chat, new_best_number, or failover_flagged
previous_chat_idSet on failover_flagged only — the abandoned chat on the old line

Two optional fields shape the pick:

  • continuation_message — text-only, sent instead of message when the send fails over to a new line. Useful as an opener that reads sensibly coming from an unfamiliar number. Ignored on reuse and on genuine first contact; exactly one message is sent either way.
  • exclude_from — lines to keep out of this send’s pick. It only affects picking a line for a new chat; an existing chat is always reused on its own line, so an exclusion never abandons a live conversation. Nothing is remembered between calls.

Unlike POST /v3/chats, this endpoint allows a link in the first message — though a link as the very first message on a freshly selected line can elevate that line’s flagging risk. Voice memos aren’t supported here; use the voice memo endpoint with a known chat ID.

See the Send Message API reference for the full request and response schema, and Just hit send: introducing managed load balancing for the background on why this is the default path.

When a conversation has to go out on a particular line — a dedicated number per brand, say — create the chat yourself with an explicit from. The API creates the conversation and sends the message in a single request:

Terminal window
curl -X POST https://api.linqapp.com/api/partner/v3/chats \
-H "Authorization: Bearer $LINQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "+12052535597",
"to": [
"+12052532136"
],
"message": {
"parts": [
{
"type": "text",
"value": "Hello! How can I help you today?"
}
]
}
}'

A few constraints on this request:

  • from is required and must be a phone number assigned to your account. Choosing the line yourself opts the conversation out of the balancing and failover above, so check the line’s reputation before you send and handle failover yourself.
  • to accepts an array — one recipient for a direct message, multiple for a group chat.
  • The first outbound message must not contain links. link parts and text parts containing URLs are rejected on POST /v3/chats — send the initial message without links, then follow up with a link preview using the returned chat ID.

See the Create Chat endpoint for the full request schema.

Once you have a chat ID, post follow-up messages directly to it. The request body is a message object with a parts array — see the Send Message API reference for the full schema and language-specific examples.

Messages use a parts array where each part is text, media, or link. You can mix text and media in a single message; link parts must be sent on their own.

Text part:

{ "type": "text", "value": "Hello!" }

Media part (direct URL):

{
"type": "media",
"url": "https://example.com/photo.jpg"
}

Media part (pre-uploaded attachment):

{
"type": "media",
"attachment_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}

Link part (rich link preview):

{
"type": "link",
"value": "https://linqapp.com"
}

Mixed message (text + image) — full request body:

{
"message": {
"parts": [
{ "type": "text", "value": "Check out this photo!" },
{ "type": "media", "url": "https://example.com/photo.jpg" }
]
}
}

Note: Media parts reference the file by url or attachment_id. MIME type is inferred server-side — you do not need to pass a mime_type field.

Limits:

  • Up to 100 parts per message
  • Up to 40 public-URL media parts per message (pre-uploaded attachments are exempt)
  • Text value max length: 10,000 characters
  • Link value (URL) max length: 2,048 characters
  • Link parts must be the only part in a message — they render as a rich preview on iMessage and RCS
  • Consecutive text parts are not allowed — separate them with media or send as individual messages

See Attachments for details on sending media files.

Apply inline styles and animations to character ranges within a text part using the text_decorations array. Each decoration specifies a range: [start, end) (inclusive–exclusive, measured in UTF-16 code units) and exactly one of style or animation. Decorations are iMessage-only and ignored on RCS and SMS (see Protocol Selection).

{
"type": "text",
"value": "Hello world",
"text_decorations": [
{ "range": [0, 5], "style": "bold" },
{ "range": [6, 11], "animation": "shake" }
]
}

Styles: bold, italic, strikethrough, underline

Animations: big, small, shake, nod, explode, ripple, bloom, jitter

Style ranges may overlap (e.g. bold + italic on the same characters), but animation ranges must not overlap with other animations or styles.

Thread messages by referencing a specific message ID:

Terminal window
curl -X POST https://api.linqapp.com/api/partner/v3/chats/{chatId}/messages \
-H "Authorization: Bearer $LINQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": {
"parts": [
{
"type": "text",
"value": "Great point!"
}
],
"reply_to": {
"message_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"part_index": 0
}
}
}'

The part_index field is optional — use it to reply to a specific part of a multipart message (0-indexed). To walk a thread after building it, see Message Details → Get thread messages.

Add iMessage screen or bubble effects (confetti, fireworks, slam, invisible ink, etc.) by including an effect object inside message. iMessage only — silently ignored on RCS and SMS. See Message Effects for the full list and an example.

Force a specific delivery protocol with preferred_service (iMessage, RCS, or SMS) inside message. If omitted, the API picks the best available. See Protocol Selection for behavior and tradeoffs.

Include an idempotency_key field inside the message object to prevent duplicate sends on retries. It goes inside the message body, not as an HTTP header. Maximum length is 255 characters.

Terminal window
curl -X POST https://api.linqapp.com/api/partner/v3/chats/{chatId}/messages \
-H "Authorization: Bearer $LINQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": {
"parts": [
{
"type": "text",
"value": "This won't be sent twice"
}
],
"idempotency_key": "unique-request-id-123"
}
}'

The same applies when creating a chat (POST /v3/chats) — the key goes inside the nested message object:

{
"from": "+12223334444",
"to": ["+15556667777"],
"message": {
"parts": [{ "type": "text", "value": "Hello" }],
"idempotency_key": "unique-request-id-123"
}
}

If a message with the same key has already been processed, the API returns the original response instead of sending again.

Tip: Use UUIDs or other globally unique values as idempotency keys, and always set one in production to handle network retries safely. The official SDKs accept the key via a method option.

Edit a single text part of a previously sent message by passing part_index (0-based) and the new text. Only text parts are editable. iMessage only — listen for the message.edited webhook to confirm the edit was applied. Editable up to 5 times within 15 minutes of the original send. See the Edit Message API reference.

Delete removes a message from Linq’s records but does not unsend it — recipients still see the message on their device. See the Delete Message API reference.