Lightweight Redis Streams event messenger for service-to-service communication.
- Simple API: Just
publish()andconsume()functions - Redis Streams: Built on battle-tested Redis infrastructure
- Consumer Groups: Multiple services can consume the same events with group-based tracking
- Automatic Retries: Configurable retry limit with exponential backoff support
- Dead-Letter Queue: Failed messages sent to
<stream>:failedfor inspection - Stale Message Recovery: Automatic reclaim of messages from crashed consumers
- Structured Schema: Messages include id, event, data, retries, and timestamps
- Type Hints: Full Python type annotations for IDE support
- Minimal Dependencies: Only Redis client required
pip install streamix-queuefrom streamix_queue import consume
def on_user_created(data):
print(f"User created: {data['user_id']}")
# If handler raises an exception, message is automatically retried
consume(
"user.created",
on_user_created,
redis_url="redis://localhost:6379/0",
stream="app.events",
group="service-a",
)from streamix_queue import publish
publish(
"user.created",
{"user_id": "123", "email": "alice@example.com"},
redis_url="redis://localhost:6379/0",
stream="app.events",
group="service-a",
)- Publish: Event sent to Redis Stream with structured schema
- Consumer Group: Maintains message delivery state and ownership
- Processing: Consumer reads and processes events
- Success: Message acknowledged (removed from pending list)
- Failure: On exception, message retried; after limit exceeded, moved to DLQ
- Dead-Letter: Permanently failed messages stored in
<stream>:failedfor debugging
Publish an event to the stream.
Parameters:
event(str): Event name (e.g., "user.created")data(dict): Event payloadredis_url(str, default="redis://localhost:6379/0"): Redis connection URLstream(str, default="app.events"): Stream namegroup(str, default="app.workers"): Consumer group name
Returns: StreamMessage object with id, event, data, retries, timestamps
Start a consumer that listens for events.
Parameters:
event(str): Event name to listen forhandler(callable): Function called with message data (or message object if it accepts 2+ args)redis_url(str, default="redis://localhost:6379/0"): Redis connection URLstream(str, default="app.events"): Stream namegroup(str, default="app.workers"): Consumer group nameconsumer(str, optional): Consumer instance name (auto-generated if None)retry_limit(int, default=3): Max retries before sending to DLQbatch_size(int, default=10): Messages per batchblock_ms(int, default=5000): Blocking timeout for XREADGROUPclaim_idle_ms(int, default=60000): Idle time threshold for stale message reclaim
Handler signature:
# Simple - receives data only
def handler(data):
pass
# Advanced - receives data and full message
def handler(data, message):
print(message.id) # Message UUID
print(message.retries) # Retry count
print(message.event) # Original event name# Service A
consume("order.placed", on_order_placed, group="service-a", consumer="worker-1")
# Service B - same event, different group
consume("order.placed", on_order_placed_b, group="service-b", consumer="worker-1")# Dev
publish("user.updated", {...}, stream="dev.events", group="dev-workers")
# Prod
publish("user.updated", {...}, stream="prod.events", group="prod-workers")consume(
"payment.processed",
handle_payment,
retry_limit=5, # More retries
block_ms=10000, # Longer blocking timeouts
claim_idle_ms=120000, # Reclaim after 2 minutes
)Every message follows this structure:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"event": "user.created",
"data": {
"user_id": "123",
"email": "alice@example.com"
},
"retries": 0,
"timestamps": {
"created_at": "2026-04-24T17:45:00+00:00",
"updated_at": "2026-04-24T17:45:00+00:00"
}
}By default, messages are retried up to 3 times. After exceeding the retry limit, they're sent to the dead-letter stream:
DLQ Stream: <stream>:failed (e.g., app.events:failed)
DLQ Message Example:
{
"id": "...",
"event": "user.created",
"data": {
"original_id": "550e8400-...",
"original_event": "user.created",
"original_data": {"user_id": "123"},
"source_stream_id": "1713982500001-0",
"retries": 3,
"error": "Traceback: Connection timeout..."
},
"retries": 3,
"timestamps": {...}
}FROM python:3.12-slim
WORKDIR /app
RUN pip install streamix-queue
COPY handlers.py .
CMD ["python", "handlers.py"]apiVersion: v1
kind: Pod
metadata:
name: streamix-consumer
spec:
containers:
- name: consumer
image: myapp:latest
env:
- name: REDIS_URL
value: "redis://redis:6379/0"
- name: STREAM
value: "app.events"
- name: GROUP
value: "service-a"- Batch Size: Increase
batch_sizefor high throughput (10-50) - Block Timeout: Increase
block_msto reduce CPU usage (5000-30000) - Consumer Instances: Run multiple consumers in the same group for parallel processing
- Redis Persistence: Enable AOF/RDB for durability
Check the consumer group pending entries:
from redis import Redis
r = Redis.from_url("redis://localhost:6379/0")
pending = r.xpending("app.events", "service-a")
print(pending)from redis import Redis
r = Redis.from_url("redis://localhost:6379/0")
failed = r.xread({"app.events:failed": "0"}, count=10)
for stream, messages in failed:
for msg_id, data in messages:
print(msg_id, data)MIT License - see LICENSE file for details
Contributions welcome! Please feel free to submit a Pull Request.
For issues, questions, or feature requests, please open an issue on GitHub.