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.

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.
Four properties most often influence how the broker processes a message:
delivery_mode — 1 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.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.
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.
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:
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.
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.
By default, RabbitMQ accepts messages up to 128 MB (max_message_size). Reset it in rabbitmq.conf if needed:
max_message_size = 52428800The 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.
For large payloads, don't send everything in a single message. Split it into chunks with ordering and identity:
Compression can significantly reduce body size for text data. Mark it with a header so consumers know the format:
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.
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.max_message_size.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!