Installation¶
Requirements¶
- Python 3.10+
- Django 4.2+
- PostgreSQL
- RabbitMQ
Install¶
pip install djoutbox
Or with uv:
uv add djoutbox
Optional dependencies¶
-
Pydantic — automatic serialization/deserialization of Pydantic models:
pip install djoutbox[pydantic]
Django setup¶
1. Add to INSTALLED_APPS¶
INSTALLED_APPS = [
...,
"djoutbox",
]
2. Configure DJOUTBOX settings¶
DJOUTBOX = {
# Required
"rmq_url": "amqp://guest:guest@localhost/",
# Optional — defaults shown
"exchange_name": "outbox",
"default_retry_delays": ("1s", "10s", "1m", "5m"),
"prefetch_count": 10,
"batch_size": 50,
"notification_timeout": 60.0,
"expiration": None,
"db_alias": "default",
"sent_archive": {
"enabled": True,
"granularity": "1d",
},
}
The DJOUTBOX dict feeds both Relay and Worker constructors via **settings.DJOUTBOX. Each ignores keys it doesn't use.
3. Run migrations¶
./manage.py migrate
This creates:
djoutbox_pending— table for unsent messagesdjoutbox_sent— range-partitioned archive tabledjoutbox_notify_insert()trigger functiondjoutbox_notify_trigger— firespg_notifyon insert intodjoutbox_pendingwhensend_after <= NOW()- Indexes on
djoutbox_pending(for efficient polling) anddjoutbox_sent(for queries)
Settings reference¶
| Key | Default | Description |
|---|---|---|
rmq_url |
(required) | RabbitMQ connection string (amqp://user:pass@host:port/vhost) |
exchange_name |
"outbox" |
RabbitMQ topic exchange name |
default_retry_delays |
("1s", "10s", "1m", "5m") |
Default retry delays for all consumers |
prefetch_count |
10 |
RabbitMQ prefetch count per consumer |
batch_size |
50 |
Messages to fetch and publish per relay batch |
notification_timeout |
60.0 |
Max seconds to wait for PG NOTIFY before checking for scheduled messages |
expiration |
None |
Default message expiration in RabbitMQ (relay-level) |
db_alias |
"default" |
Which DATABASES entry to use for the relay |
sent_archive.enabled |
True |
Whether to move sent messages to djoutbox_sent (false = delete) |
sent_archive.granularity |
"1d" |
Partition granularity: "Nd" (N days) or "Nm" (N months) |
Validation¶
Settings are validated on:
- Django startup —
DjoutboxConfig.ready()callsvalidate_settings(), raisingImproperlyConfiguredon bad values build_dsn()call — the relay entrypoint validates settings even without full Django app loading
Database schema¶
djoutbox_pending¶
| Column | Type | Notes |
|---|---|---|
id |
BIGINT GENERATED BY DEFAULT AS IDENTITY |
Primary key |
routing_key |
TEXT |
RabbitMQ routing key |
body |
BYTEA |
Serialized message body |
tracking_ids |
JSONB |
Chain of UUIDs for tracing |
created_at |
TIMESTAMPTZ |
When the message was created |
send_after |
TIMESTAMPTZ |
Not sent before this time (for eta) |
expiration |
INTERVAL |
Optional TTL in RabbitMQ |
Index: (send_after, created_at) for efficient polling.
djoutbox_sent (partitioned)¶
| Column | Type | Notes |
|---|---|---|
id |
BIGINT |
Copied from pending |
routing_key |
TEXT |
|
body |
BYTEA |
|
tracking_ids |
JSONB |
|
created_at |
TIMESTAMPTZ |
Partition key |
send_after |
TIMESTAMPTZ |
|
expiration |
INTERVAL |
|
sent_at |
TIMESTAMPTZ |
When the relay published it |
Partitioned by RANGE (created_at). Partitions are created automatically by the relay. Indexes on created_at and routing_key propagate to all partitions.