Appearance
Send a message
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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
spool | path | IdentName, 1–63 chars, ^[A-Za-z0-9_:-]+$ | yes | The spool name. |
queue | path | IdentName, 1–63 chars, ^[A-Za-z0-9_:-]+$ | yes | The queue name. |
handle | query | boolean | no | When 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-Seconds | header | integer, 0–604,800 | no | Seconds 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-String | header | string, 1–128 chars, ^[\x21-\x7E]+$ | no | Any 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-Hash | header | string, 32 chars, ^[0-9a-fA-F]{32}$ | no | The 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
| Status | Description |
|---|---|
200 | Dedup hit: a message with this dedup key is already in the window; nothing was appended and there is no body. |
201 | Appended durably; no body. |
400 | The request is malformed; the kind names the problem. |
401 | The API key is missing or not recognized. |
402 | The account is suspended. |
403 | A plan limit refuses the operation; the kind names it. A 403 without a kind, on any operation, means the API key is blocked. |
404 | The spool or queue does not exist; the kind says which. |
409 | Concurrent state refuses the operation; the kind names it. |
411 | The request has no Content-Length: the header is absent or the body is chunked. An empty message needs an explicit 0. |
413 | The message exceeds the size limit. |
429 | The 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. |
500 | The server failed; kind operation_unconfirmed means the write was accepted but its durability is unknown. |
503 | The spool is momentarily unavailable. |
507 | The 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
| Header | Description |
|---|---|
Spooler-Message-Id | The original message's id. |
201 headers
| Header | Description |
|---|---|
Spooler-Message-Id | The appended message's id. |
Spooler-Handle | The 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
| Header | Description |
|---|---|
Retry-After | Seconds until the next request is admitted, whole and at least 1; always the delay-seconds form, never an HTTP-date. |