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".| Value | Channel | Addresses tried (from lead) |
|---|---|---|
"imessage" | iMessage via device relay | phone → altPhone1-3 → email → altEmail1-3 |
"sms" | SMS via Twilio | phone → altPhone1-3 only |
"email" | email → altEmail1-3 only |
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.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.| Header | Purpose |
|---|---|
x-correlation-id | Request-scoped tracing ID (preferred over body field). Echoed back in Loki under correlationId. |
x-execution-id | GHL workflow execution ID. Only set when calling from a GHL workflow context. |
x-ghl-workflow-id | GHL workflow definition ID. Pairs with x-execution-id. |
Idempotency-Key | When supplied, Tuco caches the response and replays it on retry within the idempotency window. Use one unique key per logical send. |
Examples
Create or import a lead first usingPOST /api/leads, then send using leadId as shown below.
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
| Status | Meaning | Is it an error? |
|---|---|---|
"sent" | Message sent immediately (sync path) | No |
"pending" | Created, worker will process. Line limits / time window / device gap may delay it. | No — it will send when checks pass |
"scheduled" | Will send at scheduledDate, or rescheduled due to device gap | No |
"fallback" | Recipient has no iMessage; fallback SMS sent if configured | No (business rule) |
"failed" | All retries exhausted or availability API error | Yes (technical) |
"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.
Errors
Errors are returned only for validation failures — never for pre-send checks.| Status | When | Example |
|---|---|---|
400 | Message and attachments both empty | { "error": "Message and attachmentUrls cannot both be empty" } |
400 | Invalid messageType | { "error": "Invalid messageType. Must be email, sms, or imessage" } |
400 | No active lines + no fromLineId | { "error": "No active lines in workspace..." } |
400 | No recipient anywhere | { "error": "Either recipientEmail or recipientPhone is required..." } |
400 | sendFallbackSmsOnFailed: true but fallback not configured | { "error": "...", "code": "FALLBACK_NOT_CONFIGURED" } |
401 | Invalid or missing API key | { "error": "Unauthorized" } |
402 | Subscription past due (workspace read-only) | { "error": "READ_ONLY", "code": "READ_ONLY", "reason": "past_due" } |
404 | leadId provided but not found | { "error": "Lead not found or access denied" } |
What happens after the API call
Message created
Tuco inserts a record in the
messages collection with status pending
(or scheduled if scheduledDate is provided).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.
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).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.
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).
| Scenario | Fallback SMS fires? |
|---|---|
Status = fallback (no iMessage), Twilio configured | Yes — always |
Status = failed, sendFallbackSmsOnFailed: true, Twilio configured | Yes |
Status = failed, sendFallbackSmsOnFailed: false (default) | No |
Status = failed, flag is true but no Twilio | 400 error at API call time |
Fallback SMS requires
recipientPhone on the message. Email-only messages
will not trigger fallback SMS.