Skip to main content

Overview

Every call to POST /api/messages creates a message document in Tuco. This page describes the shape of that document, the lifecycle it moves through, and how delivery works under the hood.
Messages sent via API appear in Unibox alongside manually sent and incoming messages. Same recipient = same thread.

Message object

string
Unique message ID (MongoDB ObjectId)
string
Body text
string
"imessage" | "sms" | "email". Defaults to "imessage" when omitted from the API.
string
Current lifecycle state. See Status lifecycle below.
string
Line used to send
string
Denormalized phone from the line
string
Denormalized email from the line
string
Recipient phone number
string
Recipient email address
string
Display name
string
Linked lead (if any)
string
Clerk organization ID
string
Clerk user ID who created the message
string
received for inbound (a reply from the lead), sent for outbound. Inbound messages are where lead-sent photos live.
string[]
Present only when the message has attachments. Public, signed, 30-day URLs — GET each one (no auth header) to download the real image/file bytes with the correct Content-Type. This is how you retrieve a photo a lead sent you: the raw attachmentUrls below point at private storage and return 403 on their own.
string[]
Filenames aligned 1:1 with attachmentDownloadUrls (e.g. ["IMG_1380.jpeg"]).
string[]
Raw storage URLs (private for inbound / Tuco-hosted). Kept for reference — use attachmentDownloadUrls to fetch bytes.
string
ISO 8601 timestamp — when to send
string
Grouping identifier
number
Campaign step index (0 = step 1)
object
Time window settings:
string
When actually sent
string
When delivery confirmed via device callback
string
When read (incoming messages only)
string
device message ID
string
Error details when failed or fallback
string[]
Addresses checked for iMessage availability (on fallback)
boolean
When true + message fails, Tuco sends fallback SMS if Twilio is configured. Default: false.
string
Campaign reference
string
HubSpot contact ID
string
HubSpot portal ID
string
GHL contact ID
string
GHL location ID
string
GHL conversation message ID
string
Creation timestamp
string
Last update timestamp

Status lifecycle

sent vs delivered: sent means the provider accepted the message. delivered means the recipient’s device confirmed receipt — this can arrive seconds or minutes later via a device callback.

Pre-send checks (why a message stays queued)

These checks run before a message is sent. None of them cause an API error — the message is accepted and waits.
You can check why a message is still queued by looking at your Loki/Grafana logs for events like individual.message.validation_failed, message.queued_limit, individual.message.device_gap_not_met, or individual.message.gap_not_met.

Retries

The “app sync fallback” path only runs when the BullMQ queue is unreachable (e.g. Redis down). In normal operation, the worker handles all sends.

Replying via API

There is no special “reply” endpoint. To reply to a conversation, send another message to the same recipient using POST /api/messages. Unibox groups messages by recipient, so the new message appears in the existing thread.

Where messages appear