# Publishers

mx-notifier forwards normalized events to external message brokers in parallel with hub WebSocket delivery. Two broker integrations are available: RabbitMQ (AMQP) and Azure Service Bus. Both can be enabled simultaneously; mx-notifier publishes events to all configured brokers on each block.

A no-op publisher is available for development and testing environments where no external broker is needed.

***

## RabbitMQ

### Overview

The RabbitMQ publisher connects to a broker over AMQP and publishes serialized events to a configured exchange. Routing keys segment event types or sources within the exchange, letting consumers bind queues to the event categories they need.

### Message format

Each message body is a serialized protobuf payload for the event type. AMQP message headers include:

| Header         | Value                                         |
| -------------- | --------------------------------------------- |
| `content-type` | `application/protobuf`                        |
| `topic`        | The notifier topic string (e.g., `SaveBlock`) |
| `shard-id`     | Shard ID of the originating block             |

### Exchange routing

Events are published with a routing key that combines the topic name and shard ID:

```
<exchange>  routing key: <topic>.<shard-id>
```

Example routing keys:

| Routing key                 | Matches                                              |
| --------------------------- | ---------------------------------------------------- |
| `SaveBlock.0`               | Block data from shard 0                              |
| `SaveBlock.1`               | Block data from shard 1                              |
| `SaveBlock.#`               | Block data from all shards (topic exchange wildcard) |
| `SaveAccounts.*`            | Account updates from any shard                       |
| `FinalizedBlock.4294967295` | Finalized block notifications from the metachain     |

{% hint style="info" %}
Use a topic exchange (not direct) to take advantage of routing key wildcards. Consumers can then subscribe to `SaveBlock.#` to receive blocks from all shards without creating separate bindings per shard.
{% endhint %}

### Configuration reference

| Parameter            | Description                                   | Example                                    |
| -------------------- | --------------------------------------------- | ------------------------------------------ |
| `url`                | AMQP connection URL                           | `amqp://user:pass@rabbitmq.internal:5672/` |
| `exchange`           | Exchange name to publish to                   | `mx-notifier`                              |
| `routing_key_prefix` | Optional prefix prepended to all routing keys | `prod`                                     |
| `enabled`            | Enable or disable the publisher               | `true`                                     |

### Durability and ordering

* Messages are published as persistent (delivery mode 2) by default, surviving broker restarts.
* Message ordering is preserved within a single routing key (queue). Cross-shard ordering is not guaranteed.
* The publisher does not wait for broker acknowledgement by default (fire-and-forget). Enable publisher confirms in the configuration to trade throughput for delivery assurance.

{% hint style="warning" %}
If the RabbitMQ connection drops, the circuit breaker engages and events queue internally with configurable backoff. Events accumulated during a broker outage replay in order when the connection is restored. Size the retry queue and backoff window to match your maximum acceptable outage duration.
{% endhint %}

***

## Azure Service Bus

### Overview

The Azure Service Bus publisher delivers events to a Service Bus topic, where multiple subscriptions independently consume the same event stream. Use this model for cloud-native architectures where multiple downstream services each need a filtered view of the event stream.

### Message format

Each Service Bus message body is the serialized protobuf payload of the event. Application properties mirror the AMQP headers:

| Property      | Value                                     |
| ------------- | ----------------------------------------- |
| `ContentType` | `application/protobuf`                    |
| `topic`       | Notifier topic string (e.g., `SaveBlock`) |
| `shardId`     | Shard ID of the originating block         |

### Topic and subscription model

One Service Bus topic receives all events from mx-notifier. Consumers create subscriptions on that topic with SQL filter rules to select the event categories they need:

```sql
-- Receive only SaveBlock events from shard 0
topic = 'SaveBlock' AND shardId = '0'

-- Receive all account updates from any shard
topic = 'SaveAccounts'

-- Receive block finalization from all shards
topic = 'FinalizedBlock'
```

### Configuration reference

| Parameter           | Description                         | Example                                                         |
| ------------------- | ----------------------------------- | --------------------------------------------------------------- |
| `connection_string` | Azure Service Bus connection string | `Endpoint=sb://...;SharedAccessKeyName=...;SharedAccessKey=...` |
| `topic_name`        | Service Bus topic to publish to     | `mx-notifier-events`                                            |
| `enabled`           | Enable or disable the publisher     | `true`                                                          |

### Ordering and sessions

Azure Service Bus does not guarantee global message ordering without sessions. To preserve per-shard ordering, configure the publisher to use session IDs derived from the shard ID:

```
session_id: <shard-id>
```

With session-enabled topics, all messages for the same shard are delivered in order to a single session-aware consumer. Consumers processing multiple shards in parallel must open one session receiver per shard.

{% hint style="warning" %}
Session-enabled topics require premium-tier Service Bus. Standard-tier topics do not support sessions and do not guarantee message ordering.
{% endhint %}

### Durability

Service Bus persists messages before the publisher receives acknowledgement. The default message lock duration is 60 seconds. If a consumer fails to settle a message within the lock window, Service Bus re-delivers it. Configure dead-letter queues on each subscription to capture messages that exhaust their delivery count.

***

## Choosing a broker

| Consideration      | RabbitMQ                                | Azure Service Bus                   |
| ------------------ | --------------------------------------- | ----------------------------------- |
| Deployment model   | Self-hosted or managed (CloudAMQP)      | Fully managed Azure service         |
| Protocol           | AMQP 0-9-1                              | AMQP 1.0 / HTTP                     |
| Delivery guarantee | At-least-once (with publisher confirms) | At-least-once                       |
| Message ordering   | Per-queue, per-routing-key              | Per-session (premium tier)          |
| Fan-out            | Exchange + multiple queue bindings      | Topic + multiple subscriptions      |
| Typical latency    | <5 ms (same datacenter)                 | 10–50 ms (varies by region)         |
| Max message size   | 128 MB (configurable)                   | 256 KB (standard) / 1 MB (premium)  |
| Replay / retention | Queue-based (no built-in replay)        | Dead-letter queue; no native replay |

Use **RabbitMQ** when:

* Your consumers are co-located with mx-notifier in the same datacenter or VPC
* You need sub-10 ms broker latency
* You operate your own infrastructure and prefer AMQP semantics

Use **Azure Service Bus** when:

* Your consumers are cloud-native Azure services
* You need managed infrastructure with no broker operations overhead
* You require independent, filtered subscriptions for multiple downstream services

***

## No-op publisher

A no-op publisher is available for local development and test environments. It accepts all events and discards them without network I/O. Enable it by setting the publisher type to `noop` in configuration. The no-op publisher logs a count of discarded events at the `DEBUG` level.
