Skip to main content

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.

When to use message queues Use message queues when your consumer can't expose a public endpoint, when you need backpressure, or when consumers should process at their own pace. For push-based webhooks where Runlater calls your URL, use Tasks instead.

How It Works

  1. Create a queue with a name, visibility timeout, and max receives
  2. Producers enqueue messages via the API — any JSON payload
  3. Consumers receive messages via HTTP long-polling — messages are locked while being processed
  4. Acknowledge to mark a message as done, or nack to return it to the queue
  5. 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=20 to 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.