RabbitMQ Message Properties

Every message in RabbitMQ carries more than just its body. It also has a set of properties — metadata that travels with the message and tells consumers and the broker how to handle it. Understanding message properties lets you control delivery behaviour, expiry, priority, and traceability without modifying the message body itself.

Message Structure Overview

+---------------------------+
|  Message                  |
|  +---------------------+  |
|  |  Properties         |  |
|  |  (metadata)         |  |
|  +---------------------+  |
|  +---------------------+  |
|  |  Body               |  |
|  |  (actual payload)   |  |
|  +---------------------+  |
+---------------------------+

The body contains the data (JSON, XML, plain text, binary). The properties describe what the message is and how it should be treated.

Standard Message Properties

content_type

Declares the MIME type of the message body. The broker does not enforce or parse this — it is purely informational for the consumer.

content_type = "application/json"

content_encoding

Specifies the encoding applied to the message body, such as gzip or utf-8. Useful when the message body is compressed.

delivery_mode

Controls whether the message is stored to disk.

  • 1 = Transient (memory only, lost on restart)
  • 2 = Persistent (written to disk, survives restart)

Set this to 2 for any message you cannot afford to lose.

priority

A number between 0 and 255 that sets the message's delivery priority. Higher values mean higher priority. This property only takes effect when the queue is declared with x-max-priority. Without that queue setting, all messages are treated equally regardless of priority value.

correlation_id

A string used to correlate a response with a request. In the request-reply pattern, the producer sets a unique correlation_id on the request message. The consumer copies this value onto the reply message. The producer uses the correlation_id to match the reply to the original request.

reply_to

The name of the queue where the consumer should send its reply. Used in the request-reply pattern alongside correlation_id.

Producer sets:
  correlation_id = "req-88291"
  reply_to = "response-queue-A"

Consumer reads message, processes it, then publishes reply to:
  exchange = ""
  routing_key = "response-queue-A"
  correlation_id = "req-88291"

Producer reads reply from "response-queue-A" and matches
correlation_id "req-88291" to identify which request got answered.

expiration

Sets a per-message time-to-live in milliseconds as a string. If the message has not been consumed before the expiry time, it is discarded or dead-lettered.

expiration = "30000"  -- expires after 30 seconds

This overrides the queue-level TTL for this specific message. The effective TTL is the minimum of the message TTL and the queue TTL.

message_id

A unique identifier for the message set by the producer. The broker does not use this value — it is available to consumers for deduplication or logging. Generate a UUID for each message and store it here.

timestamp

A Unix epoch timestamp indicating when the message was created. Useful for measuring end-to-end message latency.

type

A free-form string describing the message type. Producers use it to signal the event type without parsing the body. For example: type = "order.placed".

user_id

If set, RabbitMQ verifies that this value matches the username of the connection that published the message. This is a security feature — it prevents one user from impersonating another as the sender of a message.

app_id

An identifier for the producing application. Useful in multi-service architectures for tracking which service sent a message.

headers

A key-value table of custom attributes. Any extra metadata that does not fit the standard properties goes here. The headers exchange uses this table for routing. Consumers also read custom headers to make processing decisions.

Setting Properties in Python

import pika

properties = pika.BasicProperties(
    content_type='application/json',
    delivery_mode=2,          # persistent
    correlation_id='req-001',
    reply_to='my-reply-queue',
    expiration='60000',       # 60 seconds
    message_id='uuid-abc123',
    timestamp=1700000000,
    type='order.placed',
    app_id='checkout-service',
    headers={
        'source': 'web',
        'region': 'eu-west'
    }
)

channel.basic_publish(
    exchange='orders',
    routing_key='order.placed',
    body='{"order_id": 9001}',
    properties=properties
)

Reading Properties in a Consumer

def callback(ch, method, properties, body):
    print(f"Message ID:      {properties.message_id}")
    print(f"Content type:    {properties.content_type}")
    print(f"Correlation ID:  {properties.correlation_id}")
    print(f"Reply to:        {properties.reply_to}")
    print(f"Custom headers:  {properties.headers}")
    print(f"Body:            {body.decode()}")
    ch.basic_ack(delivery_tag=method.delivery_tag)

Properties vs Headers: What Is the Difference

Standard properties (like content_type, delivery_mode) are part of the AMQP protocol specification. Every AMQP client understands them. The headers property is a free-form key-value table for custom data that does not fit the standard fields. Think of standard properties as the printed fields on an official form, and the headers table as the blank "notes" section where you write anything extra.

Summary

Message properties are metadata that travel with every RabbitMQ message. Key properties include delivery_mode for persistence, expiration for TTL, correlation_id and reply_to for request-reply workflows, and headers for custom attributes. Setting the right properties makes your messages self-describing, traceable, and correctly prioritised. Consumers can act on properties without parsing the message body, which keeps business logic clean and fast.

Leave a Comment

Your email address will not be published. Required fields are marked *