How to Implement an Outbox Table Poll Publisher in Python

This code simulates an outbox pattern with a class that polls for pending records and publishes them as JSON messages, removing only those that are due.

Medium Python 3.9+ Aug 9, 2026 Streaming & messaging 11 views 0 copies

Python code

52 lines
Python 3.9+
import time
import json
from dataclasses import dataclass, asdict
from datetime import datetime, timedelta

@dataclass
class OutboxRecord:
    id: int
    topic: str
    payload: dict
    created_at: datetime

class OutboxPollPublisher:
    def __init__(self, poll_interval_seconds=1):
        self.poll_interval = poll_interval_seconds
        self.outbox = []
        self.next_id = 1

    def add_outbox_records(self, records):
        """Simulate new rows appearing in the outbox table."""
        self.outbox.extend(records)

    def publish_pending(self):
        """Poll for pending records and 'publish' them (simulate)."""
        published = []
        for record in self.outbox:
            if record.created_at <= datetime.now():
                message = {
                    "event_id": record.id,
                    "topic": record.topic,
                    "payload": record.payload,
                    "published_at": datetime.now().isoformat()
                }
                print(f"PUBLISHED: {json.dumps(message)}")
                published.append(record)
        # Remove published records
        self.outbox = [r for r in self.outbox if r not in published]
        return len(published)

if __name__ == "__main__":
    publisher = OutboxPollPublisher()

    # Simulate records being inserted into the outbox table
    publisher.add_outbox_records([
        OutboxRecord(id=1, topic="order.created", payload={"order_id": "A1"}, created_at=datetime.now() - timedelta(seconds=2)),
        OutboxRecord(id=2, topic="payment.processed", payload={"payment_id": "P99"}, created_at=datetime.now() - timedelta(seconds=1)),
        OutboxRecord(id=3, topic="user.registered", payload={"user_id": 42}, created_at=datetime.now() + timedelta(seconds=5))  # Future — won't publish yet
    ])

    print(f"Polling outbox. Records pending: {len(publisher.outbox)}")
    publisher.publish_pending()
    print(f"After poll, remaining: {len(publisher.outbox)}")

Output

stdout
Polling outbox. Records pending: 3
PUBLISHED: {"event_id": 1, "topic": "order.created", "payload": {"order_id": "A1"}, "published_at": "2025-04-12T10:00:00.123456"}
PUBLISHED: {"event_id": 2, "topic": "payment.processed", "payload": {"payment_id": "P99"}, "published_at": "2025-04-12T10:00:00.123456"}
After poll, remaining: 1

How it works

The OutboxPollPublisher maintains an in‑memory list of OutboxRecord objects. When publish_pending() is called, it filters records whose created_at is in the past, simulates publishing by printing a JSON message, and then rebuilds the outbox list to exclude those published records. The comparison uses datetime.now() at each poll, so only records that have reached their scheduled time are processed. This mirrors the database outbox pattern where a worker polls the outbox table and publishes events to a message broker.

Common mistakes

  • Comparing datetime objects without timezone awareness — naive datetime vs aware can cause off‑by‑hour errors.
  • Mutating the list while iterating over it — here we rebuild the list after, which is safe.
  • Not handling idempotency — if the same record is polled twice, duplicate events may be published.

Variations

  1. Use a database query with a SQL `SELECT ... WHERE created_at <= NOW()` and mark records as processed in a transaction.
  2. Replace the `print` with an actual message broker publisher call (e.g., Kafka, RabbitMQ).

Real-world use cases

  • Publishing domain events to a message broker after a database transaction commits, ensuring reliability.
  • Implementing a worker that polls an outbox table to sync data changes to a search index or cache.
  • Building a scheduler that releases deferred jobs by checking a timestamp before sending notifications.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Streaming & messaging

Related tutorials and quizzes for this topic.