# How to Publish Messages with Routing Keys in Kourier: A Complete Guide

> Learn to publish messages with routing keys in Kourier using basicPublish. This guide details how routingKey directs messages to specific queues via exchange and binding configurations.

- Repository: [Guimauve Digital/kourier](https://github.com/guimauvedigital/kourier)
- Tags: how-to-guide
- Published: 2026-03-02

---

**Kourier exposes routing key functionality through the `AMQPChannel.basicPublish()` method, where the `routingKey` parameter determines which bound queues receive the message based on the exchange type and binding configuration.**

The Kourier library (guimauvedigital/kourier) provides a Kotlin multiplatform AMQP 0-9-1 client that implements standard message routing semantics. When publishing messages, the routing key acts as the address label that exchanges use to determine message delivery paths to specific queues.

## The `basicPublish` API and Routing Key Semantics

Kourier follows the AMQP 0-9-1 model where a **publisher** sends a message to an **exchange** together with a **routing key**. The exchange uses that key (and the exchange type) to decide which queues receive the message.

The publishing API is exposed via the `AMQPChannel` interface in [`amqp-client/src/commonMain/kotlin/dev/kourier/amqp/channel/AMQPChannel.kt`](https://github.com/guimauvedigital/kourier/blob/main/amqp-client/src/commonMain/kotlin/dev/kourier/amqp/channel/AMQPChannel.kt):

```kotlin
suspend fun basicPublish(
    body: ByteArray,
    exchange: String,
    routingKey: String,
    mandatory: Boolean = false,
    immediate: Boolean = false,
    properties: Properties = Properties()
): AMQPResponse.Channel.Basic.Published

```

- **`exchange`** – Name of the target exchange (empty string uses the default direct exchange).
- **`routingKey`** – The string that the exchange matches against its **bindings**.
- **`mandatory`** / **`immediate`** – Optional flags that affect undeliverable messages.

The concrete implementation in [`DefaultAMQPChannel.kt`](https://github.com/guimauvedigital/kourier/blob/main/DefaultAMQPChannel.kt) encodes the routing key into the `Basic.Publish` frame before sending the payload to the broker (lines 31-45).

## Publishing to a Direct Exchange

To publish messages with routing keys in Kourier, declare a direct exchange and invoke `basicPublish` with the desired key. The following example from the official tutorial demonstrates publishing log messages with severity levels as routing keys:

```kotlin
val config = amqpConfig { server { host = "localhost" } }
val connection = createAMQPConnection(this, config)
val channel = connection.openChannel()

// Declare a direct exchange named "direct_logs"
channel.exchangeDeclare(
    "direct_logs",
    BuiltinExchangeType.DIRECT,
    durable = false,
    autoDelete = false,
    internal = false,
    arguments = emptyMap()
)

// Publish a message with severity "error" as the routing key
channel.basicPublish(
    body = "Disk space critically low".toByteArray(),
    exchange = "direct_logs",
    routingKey = "error",               // ← routing key
    properties = Properties()
)

channel.close()
connection.close()

```

*Implementation reference:* This pattern appears in the `emitLogDirect` function demonstrated in [`docs/tutorials/routing.md`](https://github.com/guimauvedigital/kourier/blob/main/docs/tutorials/routing.md) (lines 96-124).

## Binding Queues to Routing Keys

Publishing with a routing key only works if queues are **bound** to the exchange with matching **binding keys**. For a direct exchange, the message is delivered only to queues whose binding key exactly matches the routing key.

```kotlin
val queue = channel.queueDeclare(
    name = "",               // temporary, server‑generated name
    durable = false,
    exclusive = true,
    autoDelete = true,
    arguments = emptyMap()
).queueName

// Bind the temporary queue to the "direct_logs" exchange for "error" and "warning"
listOf("error", "warning").forEach { severity ->
    channel.queueBind(
        queue = queue,
        exchange = "direct_logs",
        routingKey = severity       // ← binding key (must match routing key)
    )
}

```

*Implementation reference:* The binding loop appears in [`docs/tutorials/routing.md`](https://github.com/guimauvedigital/kourier/blob/main/docs/tutorials/routing.md) (lines 68-74). The routing semantics for direct exchanges are documented in the same file (lines 46-48).

## Consuming Messages and Accessing Routing Keys

When a message is received, the broker constructs an `AMQPMessage` that exposes the original routing key via the `routingKey` property. Consumers can inspect this value to determine how the message was routed.

```kotlin
val consumer = channel.basicConsume(queue, noAck = true)

for (delivery in consumer) {
    val rk = delivery.message.routingKey   // ↔ the routing key used when publishing
    val payload = delivery.message.body.decodeToString()
    println("Received [$rk]: $payload")
}

```

*Implementation reference:* The `AMQPMessage` data class in [`AMQPMessage.kt`](https://github.com/guimauvedigital/kourier/blob/main/AMQPMessage.kt) (lines 4-6) defines the `routingKey` field. The consumer loop pattern appears in [`docs/tutorials/routing.md`](https://github.com/guimauvedigital/kourier/blob/main/docs/tutorials/routing.md) (lines 81-88).

## Exchange Types and Routing Behavior

The routing key only matters if the exchange type uses it:

- **Direct exchange** – Delivers messages to queues where the binding key exactly matches the routing key.
- **Topic exchange** – Uses the routing key for pattern matching against binding keys (wildcards supported).
- **Fanout exchange** – Ignores the routing key entirely and broadcasts to all bound queues.

## Summary

- **Use `AMQPChannel.basicPublish()`** with the `routingKey` parameter to publish messages with routing keys in Kourier.
- **Declare exchanges** using `BuiltinExchangeType.DIRECT` or `TOPIC` to enable routing key matching.
- **Bind queues** using `queueBind()` with a binding key that matches your publishing routing keys.
- **Access routing keys** during consumption via `delivery.message.routingKey` in the `AMQPMessage` object.
- **Reference [`RoutingTest.kt`](https://github.com/guimauvedigital/kourier/blob/main/RoutingTest.kt)** (lines 39-84) for an end-to-end integration test verifying routing behavior.

## Frequently Asked Questions

### What happens if I publish a message with an empty routing key?

When publishing to the default direct exchange (empty string exchange name), an empty routing key matches queues bound with an empty binding key. For named exchanges, an empty routing key only delivers to queues explicitly bound with an empty string binding key, which is uncommon in production systems.

### How does Kourier handle routing keys for topic exchanges?

Kourier supports standard AMQP topic exchange semantics where routing keys are dot-separated strings (e.g., `stock.nyse.tech`). Consumers can use wildcard binding keys such as `stock.*` or `stock.#` to receive subsets of messages. The `routingKey` parameter in `basicPublish()` accepts any valid topic routing key string.

### Can consumers retrieve the routing key from incoming messages?

Yes. The `AMQPMessage` class in [`AMQPMessage.kt`](https://github.com/guimauvedigital/kourier/blob/main/AMQPMessage.kt) exposes the `routingKey` property, allowing consumers to access the exact routing key used during publishing via `delivery.message.routingKey`. This enables content-based routing decisions or logging within the consumer application.

### Where is the routing key encoding implemented in the Kourier source code?

The routing key is encoded into the AMQP `Basic.Publish` frame in [`DefaultAMQPChannel.kt`](https://github.com/guimauvedigital/kourier/blob/main/DefaultAMQPChannel.kt) (lines 31-45). This implementation takes the `routingKey` string from the `basicPublish()` call and writes it into the protocol frame sent to the broker, ensuring compatibility with standard AMQP 0-9-1 brokers.