Learn RabbitMQ - Message Properties & Headers
Episode 9 of 33

Learn RabbitMQ - Message Properties & Headers

A RabbitMQ message carries more than just a body. In this episode you learn all the standard AMQP properties such as delivery_mode, priority, expiration, and correlation_id, add custom headers for metadata, and strategies for handling large messages with chunking and compression.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

So far we've used messages as raw bodies. In fact, an AMQP message carries properties — structured metadata that tells the broker and consumers how the message should be treated. There are 14 standard properties in AMQP 0-9-1, and each has a specific role.

Some properties determine broker behavior: delivery_mode marks a message persistent, expiration sets a TTL, and priority controls queue ordering. Others are business metadata: correlation_id, message_id, app_id, and timestamp. Understanding all of them lets you design informative messages without leaking details into the body.

Besides properties, this episode covers custom headers for free-form metadata, and the matter of message size: RabbitMQ's default limit, strategies for handling large payloads, and compression. This is important groundwork because message size is often a source of performance problems in production.

Standard Message Properties

Properties for Broker Behavior

Four properties most often influence how the broker processes a message:

  • delivery_mode1 for transient, 2 for persistent (written to disk).
  • priority — 0 to 255, determines message ordering in a priority queue.
  • expiration — per-message TTL in milliseconds; once exceeded, the message is considered dead.
  • type — the message type name, for example order.created, useful for consumers handling many types.
PythonPublish with full properties
properties = pika.BasicProperties(
    content_type="application/json",
    delivery_mode=2,
    priority=5,
    expiration="60000",
    type="order.created",
)
channel.basic_publish(exchange="orders", routing_key="order.created",
                      body=b'{"id": "A-001"}', properties=properties)

The message above is persistent, has priority 5, and expires after 60 seconds.

Properties for Tracing and Routing

  • correlation_id — pairs requests with responses (used in episode 8).
  • message_id — a unique message ID for deduplication and tracing.
  • timestamp — the time the message was created.
  • reply_to — the queue name for the response.
  • user_id — the name of the publishing user; if set, the broker validates it.
  • app_id — the publisher application's identity, useful for observability.

Best practice: set message_id and app_id on every message. Both help a lot when tracing a message's journey across a distributed system.

Custom Headers

Adding Metadata to a Message

Besides the standard properties, you can add free-form headers — any key-value pairs carried in the headers property. This is the place for business metadata like tenant ID, origin region, or schema version:

PythonAdd custom headers
properties = pika.BasicProperties(
    headers={
        "tenant_id": "acme",
        "region": "ap-southeast-1",
        "schema_version": "v2",
    }
)
channel.basic_publish(exchange="orders", routing_key="order.created",
                      body=b'{}', properties=properties)

The headers above can be read by consumers without parsing the body, and can be used for headers exchange routing as in episode 7.

Custom Header Use Cases

Headers are useful for: metadata-based routing, filtering at the consumer, observability (trace ids), and schema evolution — older consumers can check schema_version before parsing the body. But don't overdo it: large headers add to message size and slow down the broker.

Message Size Considerations

Message Size Limits

By default, RabbitMQ accepts messages up to 128 MB (max_message_size). Reset it in rabbitmq.conf if needed:

Change the message size limit
max_message_size = 52428800

The value above limits messages to 50 MB. Keep in mind: giant messages eat up memory while being buffered, and the queue store writes them more slowly.

Large Message Strategies and Chunking

For large payloads, don't send everything in a single message. Split it into chunks with ordering and identity:

  • Send metadata in the headers: total chunks, chunk number, and message id.
  • The consumer reassembles the message after all chunks arrive.
  • If no consumer needs the pieces, offload to object storage (S3) and send only a URL reference through the broker.

Payload Compression

Compression can significantly reduce body size for text data. Mark it with a header so consumers know the format:

Pythongzip compression with a header marker
import gzip, json
 
body = json.dumps({"orders": list(range(1000))}).encode()
compressed = gzip.compress(body)
 
properties = pika.BasicProperties(
    headers={"content-encoding": "gzip"},
)
channel.basic_publish(exchange="", routing_key="data_queue",
                      body=compressed, properties=properties)

The gzip.compress call compresses the body before it's sent, and consumers handle it accordingly based on the content-encoding header.

Tip

Also use the official content_encoding property to mark compression, so other client SDKs reading the message immediately know how to decompress it.

Conclusion

In episode 9 you understood all the standard AMQP properties, added custom headers for business metadata and observability, and put together strategies for large messages: size limits, chunking, and payload compression.

Key takeaways:

  • delivery_mode=2 marks a message persistent.
  • priority and expiration change broker behavior.
  • correlation_id and message_id are important for tracing and RPC.
  • app_id and timestamp help cross-service observability.
  • Custom headers store metadata without touching the body.
  • The default message limit is 128 MB; reset it with max_message_size.
  • Large messages are split into chunks or replaced by an object storage reference.
  • Compression shrinks large payloads with a marker in the header.

In the next episode we will cover queue features and configuration — a comparison of classic queues, quorum queues, stream queues, and priority queues, complete with arguments like max length, TTL, overflow behavior, and the durable, exclusive, and auto-delete declaration types. Your choice of queue type determines the reliability and performance of your system!