AMQP routing keys are less about where a message goes and more about what it looks like to the consumers that might be interested.

Let’s see this in action. Imagine a producer sending messages about different types of fruit:

import pika

connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

channel.exchange_declare(exchange='fruit_exchange', exchange_type='direct')

message = "An apple a day..."
routing_key = 'apple'
channel.basic_publish(exchange='fruit_exchange',
                      routing_key=routing_key,
                      body=message)
print(f" [x] Sent '{message}' with routing key '{routing_key}'")

message = "Bananas are yellow."
routing_key = 'banana'
channel.basic_publish(exchange='fruit_exchange',
                      routing_key=routing_key,
                      body=message)
print(f" [x] Sent '{message}' with routing key '{routing_key}'")

connection.close()

Now, on the consumer side, we can bind queues to this exchange with specific routing keys. A consumer interested only in apples would set up its queue like this:

import pika

connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

channel.exchange_declare(exchange='fruit_exchange', exchange_type='direct')

result = channel.queue_declare(exclusive=True)
queue_name = result.method.queue

# Bind the queue to the exchange with a specific routing key
channel.queue_bind(exchange='fruit_exchange',
                   queue=queue_name,
                   routing_key='apple')

print(' [*] Waiting for messages. To exit press CTRL+C')

def callback(ch, method, properties, body):
    print(f" [x] Received {body.decode()} on routing key {method.routing_key}")

channel.basic_consume(queue=queue_name,
                      on_message_callback=callback,
                      auto_ack=True)

channel.start_consuming()

When the producer sends a message with routing_key='apple', it lands in this consumer’s queue. If the producer sends a message with routing_key='banana', it bypasses this queue entirely.

The core problem AMQP routing keys solve is enabling flexible message distribution without the producer needing to know the exact queues or consumers. The producer only needs to know what kind of information it’s sending, represented by the routing key. The broker, through the direct exchange type and queue bindings, handles the actual delivery logic.

Internally, a direct exchange routes messages based on an exact match between the routing_key on the message and the routing_key specified when a queue is bound to that exchange. If a queue is bound with routing_key='apple', it will only receive messages published to that exchange with routing_key='apple'. If multiple queues are bound with the same routing key, all of them will receive a copy of the message.

A crucial detail is that the routing_key is a string, and its interpretation is entirely up to the binding. You can use it for anything from simple identifiers like 'apple' to more complex patterns if you use a topic exchange, where wildcards like *.orange or orange.* become significant. With a direct exchange, however, it’s a strict, literal match.

The next step is understanding how to combine multiple routing keys or use wildcards for more sophisticated filtering.

Want structured learning?

Take the full Amqp course →