Transactional outbox pattern for reliable event publishing. Use when implementing atomic writes with event delivery, ensuring exactly-once semantics, or building event-driven microservices.
View on GitHubyonatangross/orchestkit
ork-backend-advanced
January 25, 2026
Select agents to install to:
npx add-skill https://github.com/yonatangross/orchestkit/blob/main/plugins/ork-backend-advanced/skills/outbox-pattern/SKILL.md -a claude-code --skill outbox-patternInstallation paths:
.claude/skills/outbox-pattern/# Outbox Pattern (2026)
Ensure atomic state changes and event publishing by writing both to a database transaction, then publishing asynchronously.
## Overview
- Ensuring database writes and event publishing are atomic
- Building reliable event-driven microservices
- Implementing exactly-once message delivery semantics
- Avoiding dual-write problems (DB + message broker)
- Decoupling domain logic from message infrastructure
- High-throughput systems needing CDC-based publishing
## Quick Reference
### Outbox Table Schema
```sql
CREATE TABLE outbox (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
aggregate_type VARCHAR(100) NOT NULL,
aggregate_id UUID NOT NULL,
event_type VARCHAR(100) NOT NULL,
payload JSONB NOT NULL,
idempotency_key VARCHAR(255) UNIQUE, -- For consumer deduplication
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
published_at TIMESTAMPTZ,
retry_count INT DEFAULT 0,
last_error TEXT
);
-- Index for polling unpublished messages
CREATE INDEX idx_outbox_unpublished ON outbox(created_at)
WHERE published_at IS NULL;
-- Index for aggregate ordering
CREATE INDEX idx_outbox_aggregate ON outbox(aggregate_id, created_at);
-- Index for idempotency key lookups
CREATE INDEX idx_outbox_idempotency ON outbox(idempotency_key)
WHERE idempotency_key IS NOT NULL;
```
### SQLAlchemy Model
```python
from sqlalchemy.dialects.postgresql import UUID, JSONB
import hashlib
class OutboxMessage(Base):
__tablename__ = "outbox"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
aggregate_type = Column(String(100), nullable=False)
aggregate_id = Column(UUID(as_uuid=True), nullable=False, index=True)
event_type = Column(String(100), nullable=False)
payload = Column(JSONB, nullable=False)
idempotency_key = Column(String(255), unique=True, nullable=True)
created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc))
published_at = Column(DateTime, nullable=True