Skip to content

Send a message

Last updated 

POST /spools/{spool}/queues/{queue}/send

Appends a message to the queue. The request body is the raw payload; Content-Length is required (411 without it, chunked bodies included) — the size is checked against the message size limit before the body is read. An empty message is sent with an explicit Content-Length of 0. With a Spooler-Dedup-String (or a precomputed Spooler-Dedup-Hash), a repeat send inside the queue's dedup window appends nothing and returns 200 with the original message's id. Retrying: every 4xx, 503 and 507 is answered before the append is attempted, so a retry of one of those can never duplicate the message, dedup key or not (most are verdicts; 409 dedup_in_flight, 429 and 503 are the transient ones). A 500 of any kind — operation_unconfirmed included — a 502 or 504, or no response at all means the append may have happened: retry only on a queue with a dedup window, carrying the same dedup key; without one a retry can append twice. The reasoning is in Retrying a send.

Sample ​

sh
curl -si -X POST "$SPOOLER_API/spools/<spool>/queues/<queue>/send" \
  -H "Authorization: Bearer $SPOOLER_KEY" \
  --data-binary '<payload>'

Parameters ​

NameInTypeRequiredDescription
spoolpathIdentName, 1–63 chars, ^[A-Za-z0-9_:-]+$yesThe spool name.
queuepathIdentName, 1–63 chars, ^[A-Za-z0-9_:-]+$yesThe queue name.
handlequerybooleannoWhen true, the response carries a handle to the fresh message in Spooler-Handle. Its one use is to take the message back before anyone receives it: present it to discard to cancel a delayed send whose condition was met in the meantime, or to withdraw a send whose surrounding work failed to commit — a cancel window, not a transaction: a producer that crashes before cancelling leaves a deliverable message. Off by default, since the handle is a bearer credential.
Spooler-Delay-Secondsheaderinteger, 0–604,800noSeconds until the message becomes visible to receivers, at most a week — longer horizons are a scheduler's job. Omitted or 0 means immediately visible. The retention clock starts at visibility, so a delay never eats into retention. A delayed send can be cancelled while it waits: ask for handle=true and discard with the handle.
Spooler-Dedup-Stringheaderstring, 1–128 chars, ^[\x21-\x7E]+$noAny stable key — a UUID, a ULID, or a business key like "order-12345"; 1-128 printable ASCII chars, case-sensitive, hashed server-side to the 16-byte fingerprint. A resend with the same key inside the queue's dedup window returns the original message (200) instead of enqueuing a duplicate. On a queue with no dedup window the send fails with 400 kind dedup_disabled — set the queue's dedupWindowSeconds or drop the key. Empty or repeated headers, or combining with Spooler-Dedup-Hash, fail with 400. Keys are matched by a 128-bit fingerprint; two different keys collide with probability 2⁻¹²⁸.
Spooler-Dedup-Hashheaderstring, 32 chars, ^[0-9a-fA-F]{32}$noThe 16-byte fingerprint verbatim, hex-encoded, for callers computing the fingerprint themselves. To dedup against Spooler-Dedup-String senders, compute the documented recipe: sha256(key) truncated to 16 bytes. On a queue with no dedup window the send fails with 400 kind dedup_disabled. Combining with Spooler-Dedup-String fails with 400.

Request body ​

Required; application/octet-stream.

Responses ​

StatusDescription
200Dedup hit: a message with this dedup key is already in the window; nothing was appended and there is no body.
201Appended durably; no body.
400The request is malformed; the kind names the problem.
401The API key is missing or not recognized.
402The account is suspended.
403A plan limit refuses the operation; the kind names it. A 403 without a kind, on any operation, means the API key is blocked.
404The spool or queue does not exist; the kind says which.
409Concurrent state refuses the operation; the kind names it.
411The request has no Content-Length: the header is absent or the body is chunked. An empty message needs an explicit 0.
413The message exceeds the size limit.
429The per-account request rate cap is exceeded. The body names the kind (rate_limited); the wait is the Retry-After header and is not repeated in the body. Headers are as structured as the body, and this one is the header HTTP defines for a wait.
500The server failed; kind operation_unconfirmed means the write was accepted but its durability is unknown.
503The spool is momentarily unavailable.
507The spool holds its plan's maximum — stored messages or payload bytes, whichever bound first (kind spool_full). Backpressure, not billing: receive and ack (or delete queues) to make room, then retry.

200 headers ​

HeaderDescription
Spooler-Message-IdThe original message's id.

201 headers ​

HeaderDescription
Spooler-Message-IdThe appended message's id.
Spooler-HandleThe handle to the fresh message; present only when the request asked for it via handle=true. Present it to discard to cancel the message before its first delivery.

429 headers ​

HeaderDescription
Retry-AfterSeconds until the next request is admitted, whole and at least 1; always the delay-seconds form, never an HTTP-date.