Message Queues
Pull-based message queues with HTTP long-polling. Producers enqueue via HTTP, consumers pull when ready. Like SQS, but simpler and EU-hosted.
How It Works
- Create a queue with a name, visibility timeout, and max receives
- Producers enqueue messages via the API — any JSON payload
- Consumers receive messages via HTTP long-polling — messages are locked while being processed
- Acknowledge to mark a message as done, or nack to return it to the queue
- Messages that fail too many times move to dead letter automatically
Concepts
Visibility Timeout
When a consumer receives a message, it becomes invisible to other consumers for the visibility timeout period (default: 30 seconds). This prevents duplicate processing. If the consumer doesn't acknowledge within the timeout, the message becomes available again.
Receipt Handle
Each received message includes a receipt_handle
— a one-time token used to acknowledge or reject the message. Receipt handles are only valid while the message is locked.
Dead Letter
Messages that are received but never acknowledged will keep returning to the queue. After being received
max_receives
times (default: 5), the message moves to the dead
state. Dead messages can be inspected and retried from the dashboard or API.
Long-Polling
Consumers can set wait=20
on the receive endpoint to hold the connection open for up to 20 seconds. If a message arrives during the wait, it's returned immediately. This is more efficient than polling.
Quick Start
1. Create a queue
curl -X POST https://runlater.eu/api/v1/mq \ -H "Authorization: Bearer pk_xxx.sk_xxx" \ -H "Content-Type: application/json" \ -d '{ "name": "order-events", "visibility_timeout_seconds": 30, "max_receives": 5 }'
2. Enqueue a message
curl -X POST https://runlater.eu/api/v1/mq/mq_YOUR_SLUG/messages \ -H "Authorization: Bearer pk_xxx.sk_xxx" \ -H "Content-Type: application/json" \ -d '{ "body": { "event": "order.created", "data": { "id": 123, "total": 49.99 } } }'
3. Receive messages (consumer)
# Long-poll: wait up to 20s for messages, receive up to 5 curl "https://runlater.eu/api/v1/mq/mq_YOUR_SLUG/receive?wait=20&max=5" \ -H "Authorization: Bearer pk_xxx.sk_xxx"
API Reference
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/v1/mq |
Create a queue |
GET |
/api/v1/mq |
List queues |
GET |
/api/v1/mq/:slug |
Get a queue |
PATCH |
/api/v1/mq/:slug |
Update a queue |
DELETE |
/api/v1/mq/:slug |
Delete a queue and all messages |
GET |
/api/v1/mq/:slug/stats |
Queue statistics |
POST |
/api/v1/mq/:slug/messages |
Enqueue a message |
POST |
/api/v1/mq/:slug/messages/batch |
Batch enqueue (max 10) |
GET |
/api/v1/mq/:slug/receive |
Receive messages |
DELETE |
/api/v1/mq/:slug/ack/:receipt |
Acknowledge a message |
POST |
/api/v1/mq/:slug/nack/:receipt |
Nack (return to queue) |
POST |
/api/v1/mq/:slug/ack/batch |
Batch acknowledge |
GET |
/api/v1/mq/:slug/messages |
List messages (peek) |
GET |
/api/v1/mq/:slug/messages/:id |
Get a message (peek) |
DELETE |
/api/v1/mq/:slug/messages/:id |
Delete a message |
POST |
/api/v1/mq/:slug/messages/:id/dead-letter |
Move message to dead letter |
POST |
/api/v1/mq/:slug/messages/:id/retry |
Retry a dead-lettered message |
DELETE |
/api/v1/mq/:slug/messages |
Purge all completed messages |
POST |
/api/v1/mq/:slug/pause |
Pause the queue |
POST |
/api/v1/mq/:slug/resume |
Resume the queue |
Interactive API docs with request/response schemas are available at /api/v1/docs (Swagger UI).
Queue Configuration
| Field | Default | Range | Description |
|---|---|---|---|
name |
— | — | Queue name (unique per organization) |
visibility_timeout_seconds |
30 | 1–43200 | How long a received message stays invisible |
max_receives |
5 | 1–100 | Max receive attempts before dead letter |
Message Lifecycle
available --[receive]--> locked --[ack]--> completed
|
|--[nack]--> available
|
|--[timeout expires]--> available
|
|--[max_receives hit]--> dead --[retry]--> available
Receive Parameters
| Parameter | Default | Range | Description |
|---|---|---|---|
max |
1 | 1–10 | Max messages to receive per request |
wait |
0 | 0–20 | Long-poll wait time in seconds |
Message Management
Beyond the receive/ack/nack flow, you can manage individual messages and clean up queues programmatically.
Delete a message
Permanently removes a message from the queue, regardless of its status.
curl -X DELETE https://runlater.eu/api/v1/mq/order-events/messages/MSG_ID \ -H "Authorization: Bearer pk_xxx.sk_xxx" # Returns 204 No Content
Move to dead letter
Manually move an available or locked message to the dead letter state. Useful for messages you know are bad and don't want retried automatically. Returns 422 if the message is already completed or dead.
curl -X POST https://runlater.eu/api/v1/mq/order-events/messages/MSG_ID/dead-letter \ -H "Authorization: Bearer pk_xxx.sk_xxx" # Returns the message with status "dead"
Retry a dead-lettered message
Resets a dead-lettered message back to available with receive_count=0, giving it a fresh start. Returns 422 if the message is not in dead letter state.
curl -X POST https://runlater.eu/api/v1/mq/order-events/messages/MSG_ID/retry \ -H "Authorization: Bearer pk_xxx.sk_xxx" # Returns the message with status "available"
Purge completed messages
Deletes all completed messages from a queue in one call. Useful for cleaning up after batch processing.
curl -X DELETE https://runlater.eu/api/v1/mq/order-events/messages \ -H "Authorization: Bearer pk_xxx.sk_xxx" # Returns {"data": {"purged": 42}}
Billing
Each enqueued message counts as one execution toward your monthly limit. Receiving, acking, and nacking are free. Unlimited queues on all tiers.
| Free | Pro | |
|---|---|---|
| Queues | Unlimited | Unlimited |
| Messages/month | 10,000 (shared with task executions) | 1,000,000 (shared with task executions) |
| Retention | 30 days | 30 days |
Best Practices
- Always acknowledge — unacked messages return to the queue after the visibility timeout and eventually move to dead letter.
-
Use long-polling
— set
wait=20to minimize requests. The server responds immediately when messages arrive. - Nack on failure — if processing fails, nack the message to make it immediately available for retry instead of waiting for the visibility timeout.
- Set visibility timeout > processing time — if processing takes 60 seconds, set the timeout to 90 or more.
- Batch enqueue — send up to 10 messages per request to reduce API calls.
- Batch ack — acknowledge multiple messages in one request after processing a batch.
- Monitor dead letters — check the dashboard or API for dead messages that need attention.