Skip to content

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 messages
  • djoutbox_sent — range-partitioned archive table
  • djoutbox_notify_insert() trigger function
  • djoutbox_notify_trigger — fires pg_notify on insert into djoutbox_pending when send_after <= NOW()
  • Indexes on djoutbox_pending (for efficient polling) and djoutbox_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:

  1. Django startup — DjoutboxConfig.ready() calls validate_settings(), raising ImproperlyConfigured on bad values
  2. 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.