Skip to main content
POST
/
api
/
conversations
/
{id}
/
messages
Send a conversation reply
curl --request POST \
  --url https://app.puffle.ai/api/conversations/{id}/messages \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "body_text": "<string>",
  "body_html": "<string>"
}
'
{
  "message": {
    "id": "33333333-4444-5555-6666-777777777777",
    "user_id": "11111111-1111-1111-1111-111111111111",
    "conversation_id": "f22e8a2c-1234-4567-89ab-cdef01234567",
    "campaign_id": null,
    "direction": "outbound",
    "message_type": "message",
    "subject": null,
    "body_text": "Great — Thursday at 2pm works.",
    "body_html": null,
    "sender_name": "Sarah",
    "sender_identifier": "[email protected]",
    "external_id": "am_msg_99",
    "sent_at": "2026-04-22T11:00:00Z",
    "created_at": "2026-04-22T11:00:00Z"
  }
}

Overview

Send a reply in a conversation and persist it. The server dispatches to the channel-specific handler: LinkedIn → Unipile sendMessageInChat, email → AgentMail replyToEmail (threaded to the latest inbound) or sendEmail if no inbound exists yet. On success the new message row is inserted and last_message_at is advanced (guarded against going backwards). The external send happens before the DB insert — if the insert fails after a successful send, the response is a 500 warning the caller not to retry.
This operation shares the URL path /api/conversations/{id}/messages with other verbs. See the sibling page for related operations on the same resource.

Authorizations

Authorization
string
header
required

Bearer authentication header of the form Bearer <token>, where <token> is your auth token.

Path Parameters

id
string<uuid>
required

Conversation UUID. Must be owned by the authenticated user — other callers get 404.

Pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$

Body

application/json

Reply payload. The channel-specific handler (LinkedIn via Unipile, email via AgentMail) picks whichever fields it supports.

body_text
string
required

Plain-text body of the reply. Trimmed server-side; must contain at least one non-whitespace character.

Minimum string length: 1
body_html
string

Optional rich HTML body. Used as-is for email replies; ignored by the LinkedIn handler (LinkedIn only supports plain text).

Response

Reply sent and persisted. message.external_id is the provider id (email) or null for LinkedIn (back-filled later).

message
object
required

The persisted outbound message row. For LinkedIn replies, external_id starts as null and is back-filled asynchronously when the Unipile webhook echoes the send.