Resources

Ticket messages

.md

Read and add messages to ticket conversations.

Each ticket contains a thread of messages. Messages can be inbound (from the customer), outbound (sent by your team via email), or internal (private notes visible only to agents). Message ids start with msg_. The {id} path parameter is the ticket id; SUP- client ids are also accepted (see Tickets > Identifiers).

List messagesGET/v1/tickets/{id}/messages

Permission: read. Returns { "data": [...], "pagination": { "limit", "offset", "total" } } with messages ordered by created_at ascending (oldest first). Each message includes its attachments under message_files: [{ id, file_name, file_size, file_type, file_url, created_at }]. Draft and scheduled messages are included; check metadata.draft_status and metadata.scheduled_status to tell them apart.

ParameterTypeDefaultDescription
limitnumber50Max 100.
offsetnumber0Pagination offset.
created_afterstring--ISO 8601 date for delta sync (only messages created at or after this date). 400 if not a valid date.

Add a messagePOST/v1/tickets/{id}/messages

Permission: write. Adds a new message to the ticket thread.

FieldTypeRequiredDescription
bodystringConditionalMessage text (legacy alias of body_text). Provide body/body_text OR body_html.
body_textstringConditionalPlain text message body. If body_html is omitted, it is generated from this text.
body_htmlstringConditionalHTML message body. If no text body is sent, the plain text is derived by stripping tags.
directionstringNoinbound (default), outbound, or internal. Any other value returns 400.
from_emailstringNoSender email stored on the message (outbound/internal). Defaults to the email of the API key owner. The email itself is always sent from the inbox address.
from_namestringNoSender display name stored on the message (outbound/internal). The email uses the inbox name as sender name.
attachmentsarrayNoArray of { fileName, fileData (base64), contentType }.
send_atstringNoISO 8601 date to schedule the email (outbound only). Must be in the future and at most 30 days ahead. Cannot be combined with is_draft.
is_draftbooleanNoSave as draft without sending (outbound or internal only; 400 for inbound). Cannot be combined with send_at.
terminal
Bash
01curl https://api.tickos.dev/v1/tickets/{id}/messages \
02 -H 'Authorization: Bearer sk_your_api_key' \
03 -H 'Content-Type: application/json' \
04 -d '{
05 "body_html": "<p>Thanks for reaching out. We are looking into this.</p>",
06 "direction": "outbound"
07 }'
UTF-8 · LF · Bashtickos.dev
direction defaults to inbound. To reply to the customer you must send "direction": "outbound". The shape { "body": "..." } is also accepted (body is an alias of body_text). Fields other than the ones listed above, such as "type", are ignored: without direction the message is stored as an inbound (customer) message and no email is sent.

Responses

StatusBodyWhen
201{ "message": {...}, "attachments": [...] }Message created (inbound, internal, or outbound sent immediately).
202{ "message": {...}, "attachments": [...], "scheduled": true, "scheduled_at": "ISO 8601" }Outbound message with send_at, queued for delivery.
201{ "data": {...}, "attachments": [...], "draft": true }Draft saved (is_draft: true).
400{ "error": "..." }Missing body, invalid direction, invalid or out-of-range send_at, invalid draft/schedule combination.
403{ "error": "..." }Account suspended or email sending suspended (bounce rate). Applies to every direction, including internal notes and drafts.
404{ "error": "Ticket not found" }Ticket not found in your workspace (or the ticket has no customer or inbox).
422{ "error": "..." }Outbound recipient is bounce-suppressed. See the note below.
429{ "error": "Limit exceeded", ... }Email sending limit (outbound) or storage limit reached.

Email delivery is asynchronous from the API point of view: if sending through the email provider fails after the message is stored, the request still returns 201 and the error is not reported in the response. An outbound message is only emailed when the ticket inbox has an email address configured; otherwise it is stored without sending.

The bounce-suppression check (422) runs after the message has been stored, the ticket status updated and the message.created webhook fired, so a 422 response still leaves the message in the thread (not emailed).

Direction behavior

  • outbound -- Sends a real email to the customer via AWS SES from the inbox address, with threading headers (In-Reply-To, References). Checks email rate limits and bounce suppressions. Moves open or review tickets to pending.
  • inbound -- Simulates a customer reply. The sender is always the ticket customer (from_email/from_name are ignored). Auto-unarchives the ticket, reopens closed/resolved tickets to open and changes pending to review.
  • internal -- Private note. No email is sent and the ticket status does not change.

Inbound, outbound and internal messages fire the message.created webhook (drafts do not until they are sent).

Scheduled messages

With send_at, the message is stored immediately with metadata { scheduled_at, scheduled_status: "pending" } and the email is queued for delivery at that time (returns 202). The ticket status change, email counter and message.created webhook happen when the request is made, not when the email is delivered. Use has_scheduled=true on GET /v1/tickets to find tickets with queued emails.

Drafts

Draft messages are saved without being sent. They are stored with metadata { draft_status: "draft", draft_saved_at }. Drafts do not trigger webhooks, status changes, or email sending until explicitly sent.

Saving a draft

Send a POST request with is_draft: true and direction outbound or internal. Attachments are uploaded normally. Returns 201 with { "data": {...}, "attachments": [...], "draft": true }.

terminal
Bash
01curl https://api.tickos.dev/v1/tickets/{id}/messages \
02 -H 'Authorization: Bearer sk_your_api_key' \
03 -H 'Content-Type: application/json' \
04 -d '{
05 "body_html": "<p>Draft response...</p>",
06 "direction": "outbound",
07 "is_draft": true
08 }'
UTF-8 · LF · Bashtickos.dev

Edit draftPATCH/v1/tickets/{id}/messages/{messageId}

Permission: write. Updates the body of a draft message. At least one of body_text or body_html is required (400 otherwise); the missing one is derived from the other. Only messages with draft_status = "draft" can be edited (400 otherwise). Returns { "data": {...} } with the updated message.

FieldTypeRequiredDescription
body_textstringConditionalUpdated plain text body.
body_htmlstringConditionalUpdated HTML body.

Send draftPOST/v1/tickets/{id}/messages/{messageId}/send

Permission: write. Sends a draft. Removes the draft metadata (adds sent_from_draft: true) and runs the normal flow for its direction: for outbound, email delivery from the inbox address, status update (open/review to pending) and email counter increment; message.created webhook for both outbound and internal. Returns 200 with { "data": {...}, "sent": true }, where data is the message with message_files. Returns 403 if the account or email sending is suspended, 422 if the recipient is bounce-suppressed and 429 if the email limit is reached.

Delete draftDELETE/v1/tickets/{id}/messages/{messageId}

Permission: write. Permanently deletes a draft message and its attachment records. Only messages with draft_status = "draft" can be deleted (400 otherwise). Returns { "id": "msg_...", "deleted": true }.

Use has_draft=true on GET /v1/tickets to list only tickets that have draft messages.

List attachmentsGET/v1/messages/{id}/attachments

Permission: read. {id} is the message id (msg_...). Returns all attachments of the message, oldest first, as { "attachments": [...], "count": n }. Each attachment includes id, message_id, file_name, file_size, file_type and file_url (use file_url to download the file; there is no separate download field). Returns 404 if the message does not belong to your workspace.

terminal
Bash
01curl https://api.tickos.dev/v1/messages/msg_5e6f.../attachments \
02 -H 'Authorization: Bearer sk_your_api_key'
UTF-8 · LF · Bashtickos.dev