Send Message
Messages
Send Message
Send an iMessage, SMS, or email to a contact — the core automation endpoint. REST endpoint in the Tuco AI iMessage API — bearer-token auth, JSON.
POST
Send Message
This is the primary endpoint for automation tools like GetSales, n8n, Make, or your own backend.
One call = one message. Tuco handles retries, delivery verification, and fallback internally.
Authentication
Pass your workspace API key as a Bearer token, or use a Clerk session token.Request body
The text content to send. Required unless
attachmentUrls is provided (you can send attachments only, or message + attachments). Personalization placeholders like {{firstName}} are resolved from the lead at send time.Optional array of URLs (max 2, max 25 MB each). When provided,
message can be empty. Supported formats: images (PNG, JPG, GIF, WebP, HEIC), video (MP4, MOV), audio (CAF), and PDF. Supported sources: any public URL, UploadThing/Blob URLs that belong to your workspace.Phone number in E.164 format (e.g.
"+12025551234").
Required unless recipientEmail or leadId is provided.Email address for email or iMessage (Apple ID).
Required unless
recipientPhone or leadId is provided.Channel to use. When omitted, defaults to
"imessage".Tuco line ID to send from. Optional — when omitted, Tuco round-robins
across your workspace’s active lines automatically.
Send to an existing lead using their stored contact details. When provided
together with
recipientPhone/recipientEmail, the body recipient is used
as the send-to address while the lead is used for linking.Display name for the recipient. Derived from the lead when omitted.
Alternate contact fields
Alternate contact fields
Stored on the lead (created or found). The worker tries these in priority
order when the primary address fails iMessage availability.
Alternate phone number 1
Alternate phone number 2
Alternate phone number 3
Alternate email 1
Alternate email 2
Alternate email 3
Scheduling & time window
Scheduling & time window
Control when the message is sent. All fields are optional.
When omitted, the message sends immediately (subject to line limits and device gaps).
ISO 8601 timestamp to send in the future (e.g.
"2025-10-15T14:30:00Z").IANA timezone for the send window (e.g.
"America/New_York"). Required
if using sendWindowStart/sendWindowEnd.Earliest time to send,
HH:mm format (e.g. "09:00").Latest time to send,
HH:mm format (e.g. "17:00").Days when sending is allowed.
0 = Sunday, 6 = Saturday.
Example: [1,2,3,4,5] for weekdays only.Fallback & advanced
Fallback & advanced
When
true, if the message ends in failed status (technical error after
retries), Tuco sends a fallback SMS via your configured Twilio number.
Returns 400 with code: "FALLBACK_NOT_CONFIGURED" if enabled but no
fallback is set up on your workspace.When
true, Tuco skips the iMessage availability check entirely and sends
the message straight via your workspace’s configured fallback — Twilio, GHL, or
a custom webhook (Settings → When iMessage isn’t available). Use it when you
already know the recipient isn’t on iMessage, or you simply want SMS.The send never touches iMessage, so it does not consume the line’s daily
cap or the availability-check budget, and the response status is "fallback".If no fallback is configured — or the fallback dispatch fails (e.g. a bad Twilio
from-number) — the message is not delivered: the response returns
"success": false with a channel-tagged error, and the message is recorded
with "fallbackSmsStatus": "failed". (This is distinct from the
sendFallbackSmsOnFailed 400 — forceFallback always returns 200 with the
success flag in the body.)Unlike sendFallbackSmsOnFailed (which is reactive — fallback only after an
iMessage send fails), forceFallback is proactive: it never attempts iMessage
in the first place.Workspace-level reply gate. When set, Tuco suppresses this send if the
contact has already replied in your workspace — the same protection
campaigns get from “stop on reply”, now available per request.
false/"false"/ omitted → always send (default, backward compatible)true/"true"→ skip if the contact has ever replied- a window string → skip only if they replied within that window:
"1m","1h","1d","2d","5d","7d","14d","1mo","1yr"(m=minute,h=hour,d=day,w=week,mo=30 days,yr=365 days)
200 with skippedOnly: true (see
Skip if the contact already replied).
An unrecognized value returns 400 INVALID_SKIP_IF_REPLIED.Free-form string to group related messages for reporting.
Optional tracing ID. If omitted, Tuco generates one (
app_…). The same ID
is preferred from the x-correlation-id request header. It flows through
every Loki event for this send so you can grep one ID and see the entire
request → gate → send → webhook chain. See
API Overview → Correlation IDs.Request headers
Request headers
Headers Tuco recognizes in addition to
Authorization.Examples
Create or import a lead first usingPOST /api/leads, then send using leadId as shown below.
Send straight from a GoHighLevel webhook
You can point a GoHighLevel Workflow → Webhook action directly at this endpoint — no GHL Marketplace app or action secret required, just your Tuco API key in theAuthorization header. GHL’s Webhook action already includes the full contact
payload; Tuco reads GHL’s native field names so the only thing you map in Custom
Data is message.
1
Add a Webhook action to your workflow
Method
POST, URL https://app.tuco.ai/api/messages.2
Add the auth header
Authorization = Bearer tuco_sk_xxxxxxxxxxxxx.3
Add Custom Data
Set
message (required). Optionally add lineId (pin a sending line — otherwise
Tuco round-robins) and attachmentUrls (a single URL or comma-separated list).This mapping is additive and backward compatible — it only fills a field you did
not already send. Existing integrations that post canonical fields (
recipientPhone,
ghlContactId, …) are completely unaffected.Response
The API always returns201 when the message is successfully created.
The status field tells you what happens next.
Success (201 Created or 200 OK when duplicate lead used)
200 with duplicateOnly: true, existingLeadIds, and leadIds so your automation can continue without treating it as an error.
true when the message was created.Current status of the message. See the status table below.
The full message document. Key fields:
Status values
"pending" and "scheduled" mean the message is accepted and queued. The worker sends it when:- Time window is satisfied (inside
sendWindowStart–sendWindowEnd) - Allowed day is satisfied (today is in
allowedDaysOfWeek) - Line limits reset (daily total or new conversations limit)
- Device gap is met (default 30s between sends from same device)
- Contact gap is met (default 45s between first messages to different contacts on same line)
Duplicate protection
Tuco automatically blocks duplicate sends — no header required. Two POSTs with identical(workspaceId, recipientPhone, recipientEmail, message) within
15 minutes are collapsed: the second one returns 200 with the original
message and does not fire a new send.
HTTP 200 — duplicate caught
duplicateOnly === true and treat it as a no-op success. The
message._id in the response is the original send, so your CRM workflow can
keep moving without re-sending.
Failed sends: if your first send ended in
status: "failed" and you
immediately retry with the same body within 15 minutes, you’ll get the failed
sibling back with duplicateOnly: true. To force a fresh send: change the
body or wait 15 minutes.Idempotency-Key is still supported and runs before the dedup guard.
Use it when your client controls retries — it gives you a 24-hour window
keyed on the exact request rather than on body content.
Skip if the contact already replied
SetskipIfReplied to avoid following up with a contact who has already responded
to you. Tuco checks your workspace’s inbound history for this recipient (phone or
email) and, if it finds a reply, does not create or send the message. This is the
per-request version of the campaign “stop on reply” rule — useful for drip
automations in n8n/Make/GHL where you don’t want to nudge someone who already wrote
back.
Values
Units:
m = minute, h = hour, d = day, w = week, mo = 30 days, yr = 365 days.
General form: <number><unit> (e.g. "3d", "36h"). Note "1m" is one minute and
"1mo" is one month.
Scope is your workspace, not a single line. A reply to any of your lines counts —
which line you send from doesn’t matter. The check is strictly scoped to your workspace
and never sees another workspace’s replies.
Request
Skip the follow-up if they replied in the last 7 days
Response when the send is skipped (200 OK)
No message is created. success is true (the request was handled — do not retry)
and skippedOnly distinguishes it from a real send.
HTTP 200 — skipped because the contact already replied
skippedOnly === true and treat it as a successful no-op. When the contact
has not replied (or replied outside the window), the request proceeds exactly as a
normal send and returns the usual 201 response.
Every decision is logged to Loki (message.api.skipped_replied,
message.api.skip_if_replied_passed, message.api.skip_if_replied_invalid) under your
correlationId, so you can audit exactly why a send was or wasn’t suppressed.
Errors
Errors are returned only for validation failures — never for pre-send checks.What happens after the API call
1
Message created
Tuco inserts a record in the
messages collection with status pending
(or scheduled if scheduledDate is provided).2
Pre-send checks (worker)
The worker checks line limits, time window, device gap, and contact gap.
If any fail, the message stays queued and is retried later — not failed.
3
Availability check
If the workspace has a line with Private API, Tuco checks whether the
recipient supports iMessage. If not → status becomes
fallback and
fallback SMS fires (when configured).forceFallback: true skips this step entirely — the message goes
straight to the configured fallback channel and never runs an availability
check (so it doesn’t consume the availability-check budget or line cap).4
Send
For individual messages: up to 5 send attempts with delivery
verification between each (polling for up to 90 seconds). For campaigns:
1 attempt per line.
5
Outcome
sent → appears in Unibox + webhook fires.
failed → error recorded + optional fallback SMS.
delivered → confirmed later via device callbacks.Lead resolution
When you send without aleadId, Tuco resolves the recipient automatically:
Lead found by phone/email
Lead found by phone/email
If an existing lead in your workspace matches the
recipientPhone or
recipientEmail, the message is linked to that lead. Any altPhone/altEmail
fields you pass will update the lead (merge, not overwrite).New lead created (Quick Sends)
New lead created (Quick Sends)
If no matching lead exists, Tuco creates one under a list called “Quick Sends”
(created automatically if it doesn’t exist). Alt contact fields are stored on
the new lead.
leadId provided
leadId provided
The lead must exist in your workspace. The message is linked to it.
recipientPhone/recipientEmail from the body override the lead’s stored
contact for this specific send.Alt contact fields
When you passaltPhone1–altPhone3 or altEmail1–altEmail3, they are
stored on the lead (not the message). The worker uses them as fallback
addresses when the primary address fails the iMessage availability check.
Priority order for messageType: "imessage":
Fallback SMS on failure
By default, when a message fails (technical error), no SMS fallback is sent. To enable:- Configure Twilio on your workspace (
GET /api/workspace/fallback-config). - Set
sendFallbackSmsOnFailed: truein the request body (per-message) or on the workspace (applies to all messages).
Fallback SMS requires
recipientPhone on the message. Email-only messages
will not trigger fallback SMS.