Transactional Inbox¶
The inbox is the inbound side of the transactional outbox. A broker delivers at-least-once. The presentation layer stores the command or event, commits that short transaction, and only then acknowledges the transport. A separate process reads the row and calls the mediator that is already registered.
Coding agents build the inbox with cqrs-eda. It is not a separate skill. The catalog is Agent skills.
Two steps¶
| Step | Who | What |
|---|---|---|
| 1. Accept | FastAPI route or FastStream subscriber | InboxRepository.add([(message_id, message)]), then commit |
| 2. Process | InboxProcessor in another process |
get_many, then RequestMediator.send or EventMediator.send |
The handler that runs in step 2 is the one bound on RequestMap or EventMap. The inbox does not add a handler whose only job is to insert the row, and it is not middleware. Middleware receives (request, handle) and runs inside RequestDispatcher. An event consumer and a route that must answer before the handler finishes never enter that chain.
sequenceDiagram
participant Edge as Presentation
participant Inbox as InboxRepository
participant DB
participant Worker as InboxProcessor
participant Mediator
participant Handler
Edge->>Inbox: add message_id and command or event
Inbox->>DB: insert status new
Edge->>DB: commit
Note over Edge: then ack broker or return 202
loop separate process
Worker->>Inbox: get_many SKIP LOCKED
Worker->>Mediator: send deserialized message
Mediator->>Handler: existing handler
Handler->>DB: business rows
Worker->>Inbox: status processed
Worker->>DB: commit business and status together
end
message_id comes from the application: a Kafka message id, a header, or Idempotency-Key. The framework does not read broker headers.
A repeat delivery inserts no second row. add returns False for that element and leaves the transaction usable, so the subscriber still acknowledges.
What this is not¶
- Not a
Commandbase and notIdempotentCommand. Commands and queries staycqrs.Request. - Not a handler, middleware, or mediator method that only writes the inbox row.
- Not one transaction that both accepts the message and applies the business change. The processor applies that change in a later session.
- Not a stored HTTP response. A repeated request gets
202again, not the handler's previous JSON.
See Usage for add, the name map, and the accept session versus the worker session. See Examples for the SQLite drain and the FastAPI and FastStream fragments.