SQS Message Attributes vs Message Body: Why Attributes Come Back Empty, the 10-Attribute Limit, and the SNS Raw Delivery Rule That Drops Messages

Logeshwaran
—

An Amazon SQS message has a body, the data your consumer works on, and up to 10 message attributes, small typed labels sent alongside it, such as OrderType=repair or Priority=2. Put the data in the body; put things you route, filter or trace by in attributes, so a consumer can decide what to do without parsing the body. The first surprise is the one behind most "my attributes are missing" questions: SQS doesn't return message attributes unless you ask for them. A receive-message without --message-attribute-names All brings back the body and no attributes at all, even though they're stored. The second is worse, because it loses messages: when SNS delivers to SQS with raw message delivery turned on, a message with more than 10 attributes is discarded. And without raw delivery, the attributes aren't SQS attributes at all; they're buried inside the body's JSON. This page shows how attributes work, the rules and limits, and working code for the CLI, Python, Node.js and Lambda, including a consumer that reads attributes from both kinds of SNS delivery.

Ethan, a developer, moved the order flow for his friend Jake's phone repair shop onto SQS: the website sends each repair order to a queue, and a worker picks it up. He tagged every message with an OrderType attribute so the worker could hand screen repairs to one technician and battery jobs to another. The worker never saw a single tag. Every message arrived with its body and nothing else.

⚡ Quick Answer

• Attributes missing? Ask for them: MessageAttributeNames=["All"]. Why they come back empty.

• Body or attribute? Data in the body; routing, filtering and tracing labels in attributes. How to decide.

• Limits: 10 attributes per message, and every part counts toward the 1 MiB message size. The rules.

• From SNS? Without raw delivery, attributes sit inside the body's JSON; with it, more than 10 attributes means the message is dropped. SNS to SQS.

• In Lambda: record["messageAttributes"]["Name"]["stringValue"]. The event shape.

Want SNS to filter on the body instead? Set FilterPolicyScope to MessageBody. Filtering.

New to SQS? It's AWS's message queue: one program puts messages in, another takes them out and processes them, and the queue holds them safely in between. Our SQS guide covers the basics.

🧭 NEW HERE? READ THESE FIRST

New to queues and messaging on AWS? These five pages make the rest of this one easy:

📌 Bookmark this; the receive call and the SNS consumer are the parts you will come back for.

Message body vs message attributes: what goes where

Both travel together and both count toward the message's size, but they do different jobs:

Message bodyMessage attributes
What it's forThe data: the order, the event, the jobLabels about the data: type, priority, source, a trace ID
FormatText: JSON, XML or anything elseUp to 10 name, type and value entries, typed as String, Number or Binary
Returned by ReceiveMessageAlwaysOnly if you ask with MessageAttributeNames
Read without parsing the bodyNoYes
SNS filter policiesOnly with FilterPolicyScope=MessageBodyYes, the default

A simple rule works for almost every design: if your consumer needs it to do the work, it goes in the body. If something needs it to decide where the work goes, or to trace it, it's an attribute. Ethan's order details, the device and the fault, belong in the body; OrderType and Priority, which decide which technician gets the job, are attributes.

Jake: "So the attribute is the sticky note on the box, and the body is what's inside."

Ethan: "Exactly. You read the note to decide which bench it goes to, without opening the box."

Send a message with attributes

With the AWS CLI, attributes go in --message-attributes as JSON. Each one has a DataType and a value:

aws sqs send-message --queue-url "$Q" --message-body '{"order":"A-1042","device":"Pixel","issue":"screen"}' --message-attributes '{"OrderType":{"DataType":"String","StringValue":"repair"},"Priority":{"DataType":"Number","StringValue":"2"},"Store":{"DataType":"String.code","StringValue":"BLR-01"}}'

Two details in that command. A Number attribute still uses StringValue: you send numbers as text, and the type tells consumers to read it as a number. And String.code shows a custom type: you can add your own label after the base type, such as Number.int or String.code, to tell consumers how to read the value.

The same message from Node.js with the AWS SDK v3:

import { SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs";

const sqs = new SQSClient({});
const queueUrl = process.argv[2];

const { MessageId } = await sqs.send(
  new SendMessageCommand({
    QueueUrl: queueUrl,
    MessageBody: JSON.stringify({ order: "A-1044", device: "Galaxy", issue: "battery" }),
    MessageAttributes: {
      OrderType: { DataType: "String", StringValue: "repair" },
      Priority: { DataType: "Number", StringValue: "1" },
    },
  })
);
console.log("sent", MessageId);

Run it as node send.mjs QUEUE_URL. In Python, boto3's send_message takes the same MessageAttributes dictionary.

Why your attributes come back empty

This is where Ethan's tags went. ReceiveMessage only returns message attributes you ask for by name. Receive without asking, and you get the body alone:

aws sqs receive-message --queue-url "$Q" --query "Messages[0].{Body:Body,Attrs:MessageAttributes}"
{
    "Body": "{\"order\":\"A-1042\",\"device\":\"Pixel\",\"issue\":\"screen\"}",
    "Attrs": null
}

Ask for them, and they're all there:

aws sqs receive-message --queue-url "$Q" --message-attribute-names All --query "Messages[0].MessageAttributes"
{
    "OrderType": {
        "StringValue": "repair",
        "DataType": "String"
    },
    "Priority": {
        "StringValue": "2",
        "DataType": "Number"
    },
    "Store": {
        "StringValue": "BLR-01",
        "DataType": "String.code"
    }
}

All and .* both mean every attribute; you can also list names, or use a prefix such as Order.*. Don't confuse this with --attribute-names (or MessageSystemAttributeNames), which asks for SQS's own system attributes, such as the receive count and timestamps. Setting that one and wondering why your custom attributes are still missing is the second most common version of this problem.

When attributes seem to be missing, check in this order:

  1. Your receive call passes MessageAttributeNames, not only AttributeNames.
  2. The producer really sent them: SendMessage returns an MD5OfMessageAttributes value only when the message had attributes.
  3. If the queue is fed by SNS, raw message delivery is on, or your code reads the attributes from the body.
  4. Your code reads the right capitalization for where it runs: StringValue from the SQS API, stringValue in a Lambda event.

Jake: "So the notes were on the boxes the whole time. The worker just never asked to see them."

Ethan: "One parameter. I'd spent an hour checking the sender."

Read attributes in Python, from SQS or SNS

This consumer asks for every attribute, and it also handles messages that arrived from SNS without raw delivery, where the attributes are inside the body. More on that difference in the next section:

import json
import sys

import boto3

sqs = boto3.client("sqs")
queue_url = sys.argv[1]

resp = sqs.receive_message(
    QueueUrl=queue_url,
    MaxNumberOfMessages=10,
    WaitTimeSeconds=5,
    MessageAttributeNames=["All"],  # without this, MessageAttributes never comes back
)
for msg in resp.get("Messages", []):
    attrs = {k: v["StringValue"] for k, v in msg.get("MessageAttributes", {}).items() if "StringValue" in v}
    body = json.loads(msg["Body"])
    if body.get("Type") == "Notification":  # SNS without raw delivery: attributes are inside the body
        attrs.update({k: v["Value"] for k, v in body.get("MessageAttributes", {}).items()})
        body = json.loads(body["Message"])
    print(f"order {body['order']}: {attrs}")
    sqs.delete_message(QueueUrl=queue_url, ReceiptHandle=msg["ReceiptHandle"])

Run it as python3 consume.py QUEUE_URL. From a queue fed directly or with SNS raw delivery, and from one fed by SNS without raw delivery, it prints the same thing:

order A-1042: {'OrderType': 'repair'}
order A-1043: {'OrderType': 'repair', 'Priority': '1'}

Binary attributes carry BinaryValue instead of StringValue, which is why the first dictionary line skips them; add them separately if you use them.

SNS to SQS: raw delivery and the 10-attribute trap

When an SNS topic delivers to an SQS queue, what arrives depends on one subscription setting, raw message delivery:

  • Off (the default): the SQS message's body is SNS's JSON envelope: your text under Message, your attributes under MessageAttributes, each as a Type and a Value, plus SNS fields such as TopicArn and Signature. The SQS message itself has no message attributes.
  • On: the body is exactly what you published, and your SNS attributes arrive as real SQS message attributes.

Here's the same published message, with one attribute, arriving at two queues. With raw delivery on:

{
    "Body": "{\"order\":\"A-1042\"}",
    "Attrs": {
        "OrderType": {
            "StringValue": "repair",
            "DataType": "String"
        }
    }
}

With raw delivery off, the SQS attributes are empty, and the body holds the envelope:

Attrs: None
Body keys: ['Message', 'MessageAttributes', 'MessageId', 'Signature', 'SignatureVersion', 'SigningCertURL', 'Timestamp', 'TopicArn', 'Type', 'UnsubscribeURL']
Body.MessageAttributes: {'OrderType': {'Type': 'String', 'Value': 'repair'}}

To set up an SNS topic that feeds a queue with attributes intact:

  1. Subscribe the queue to the topic, and give the queue a policy that lets the topic send to it.
  2. Turn on raw message delivery for that subscription, with the command below.
  3. Keep every published message to 10 attributes or fewer.
  4. Add a filter policy if the queue should only get some messages.

Turn raw delivery on for a subscription with:

aws sns set-subscription-attributes --subscription-arn "$SUBSCRIPTION_ARN" --attribute-name RawMessageDelivery --attribute-value true

And here's the trap. SQS messages can't have more than 10 attributes, so with raw delivery on, SNS can only pass along 10. A message published with more than 10 attributes to a topic with a raw-delivery SQS subscription isn't trimmed or delayed: SNS discards it for that subscription and treats it as a client-side error. If some messages never arrive and others do, count their attributes. Keep SNS messages to 10 attributes or fewer, or turn raw delivery off for that subscription and read the attributes from the body, as the consumer above does.

Filtering on attributes or on the body

SNS subscription filter policies look at message attributes by default, so a subscription can receive only repair orders without anyone parsing the body:

aws sns set-subscription-attributes --subscription-arn "$SUBSCRIPTION_ARN" --attribute-name FilterPolicy --attribute-value '{"OrderType":["repair"]}'

To filter on fields inside a JSON body instead, set the subscription's FilterPolicyScope to MessageBody, then write the policy against the body's fields:

aws sns set-subscription-attributes --subscription-arn "$SUBSCRIPTION_ARN" --attribute-name FilterPolicyScope --attribute-value MessageBody

aws sns set-subscription-attributes --subscription-arn "$SUBSCRIPTION_ARN" --attribute-name FilterPolicy --attribute-value '{"issue":["battery"]}'

Published with {"order":"A-2001","issue":"battery"} and {"order":"A-2002","issue":"screen"}, only A-2001 reached the battery queue. Attributes are still the lighter choice when you control the publisher: the filter doesn't depend on the body's structure, and the body can change without breaking it.

Jake: "So the post office can sort by the note, or open the box and look inside."

Ethan: "It can do both. Sorting by the note is quicker and never breaks when the box changes."

Read attributes in a Lambda function

When Lambda reads from an SQS queue, each record in the event carries its attributes under messageAttributes, with a lowercase stringValue and dataType. Notice the different capitalization from the SQS API:

import json


def lambda_handler(event, context):
    for record in event["Records"]:
        attrs = {name: a.get("stringValue") for name, a in record.get("messageAttributes", {}).items()}
        order = json.loads(record["body"])
        print(f"order {order['order']} type={attrs.get('OrderType')} priority={attrs.get('Priority')}")
    return {"processed": len(event["Records"])}
order A-1042 type=repair priority=2

Lambda requests the attributes for you, so there's no MessageAttributeNames to set. If the queue is fed by SNS without raw delivery, the attributes are still inside record["body"], exactly as in the Python consumer above.

The rules and limits

RuleDetail
How manyUp to 10 attributes per message
SizeNames, types and values all count toward the 1 MiB maximum message size, together with the body
Name charactersLetters, digits, underscore, hyphen and period
Name lengthUp to 256 characters
Name restrictionsCan't start with AWS. or Amazon. in any capitalization; can't start or end with a period, or have two periods in a row; must be unique in the message
CaseNames are case-sensitive: OrderType and ordertype are different attributes
TypesString, Number, Binary, optionally with a custom suffix such as Number.int
Empty valuesThe name, type and value must not be empty, and neither may the body

Break one of these on SendMessage, such as an eleventh attribute or an empty value, and SQS rejects the whole message, so check the error in your producer's logs. The 1 MiB limit is new enough that some queues and publishers still enforce the older 256 KiB; our message size guide covers that error.

Troubleshooting by symptom

MessageAttributes is missing or null in ReceiveMessage

Add MessageAttributeNames=["All"] (or --message-attribute-names All). Without it, SQS returns the body only.

I set AttributeNames and still don't get my attributes

AttributeNames is for SQS system attributes. Custom attributes need MessageAttributeNames.

Messages from SNS have no SQS attributes

Raw message delivery is off, so the attributes are inside the body's JSON under MessageAttributes. Turn raw delivery on, or read them from the body.

Some SNS messages never reach the queue

With raw delivery on, SNS discards messages with more than 10 attributes for that subscription. Reduce them to 10 or turn raw delivery off.

My Lambda code can't find StringValue

In Lambda events the keys are lowercase: messageAttributes, stringValue, dataType.

SNS filter policy matches nothing

By default it filters on message attributes. If the field is in the JSON body, set FilterPolicyScope to MessageBody.

SQS message attributes: frequently asked questions

What are SQS message attributes?

Up to 10 typed name and value pairs sent alongside a message body, used for metadata such as type, priority or a trace ID, which consumers can read without parsing the body.

What is the difference between SQS message attributes and the message body?

The body is the data your consumer processes. Attributes are labels about it, used for routing, filtering and tracing. The body is always returned; attributes only when requested.

Why are my SQS message attributes empty?

ReceiveMessage only returns message attributes you request. Pass MessageAttributeNames with All, .* or the names you want.

How many message attributes can an SQS message have?

Up to 10. With more, SQS rejects the message, and SNS discards it for SQS subscriptions that use raw message delivery.

Do message attributes count toward the SQS message size?

Yes. Every part of every attribute, name, type and value, counts toward the 1 MiB maximum message size along with the body.

What data types do SQS message attributes support?

String, Number and Binary, each optionally with a custom label after a period, such as Number.int or String.code. Numbers are sent as text in StringValue.

How do I read SQS message attributes in Lambda?

Each record has messageAttributes; read record["messageAttributes"]["Name"]["stringValue"]. Lambda requests the attributes for you.

How do I get SNS message attributes in SQS?

Turn on raw message delivery for the subscription, and they arrive as SQS message attributes. Without it, they're in the body's JSON under MessageAttributes, each with a Type and Value.

What is SNS raw message delivery?

A subscription setting that delivers your published message as-is, without SNS's JSON envelope. For SQS subscriptions, your attributes then become SQS message attributes, up to 10.

Can SNS filter on the message body?

Yes. Set the subscription's FilterPolicyScope to MessageBody. The default, MessageAttributes, filters on attributes.

What is the difference between AttributeNames and MessageAttributeNames?

AttributeNames (or MessageSystemAttributeNames) returns SQS system attributes such as ApproximateReceiveCount. MessageAttributeNames returns the custom attributes you sent.

Are SQS message attribute names case-sensitive?

Yes. OrderType and ordertype are different attributes. Names can't start with AWS. or Amazon. in any capitalization.

Can an SQS message attribute be empty?

No. The name, type and value must all be non-empty, and so must the message body.

Should I put metadata in SQS attributes or the body?

Attributes, if something needs it to route, filter or trace the message without reading the body. Anything the consumer needs to do its work belongs in the body.

How do I send SQS message attributes with boto3?

Pass MessageAttributes to send_message, for example {"OrderType": {"DataType": "String", "StringValue": "repair"}}.

What does .* mean in MessageAttributeNames?

All attributes, the same as All. A prefix such as Order.* returns every attribute whose name starts with Order.

Ethan's fix was one parameter in the worker's receive call, and the tags appeared at once. A week later, when the website started publishing through an SNS topic so the accounting system could get a copy, he turned on raw delivery for the worker's subscription, kept every message to four attributes, and gave the consumer the two-shape code from this page, just in case. Screen repairs now land on one bench and batteries on the other, sorted by a sticky note nobody has to open the box to read.

📌 If you keep one line from this page

SQS message attributes only come back when you ask with MessageAttributeNames, and from SNS they're only real attributes with raw delivery, where more than 10 gets the message dropped.

Body = the data · attributes = labels (up to 10) · receive with All · Lambda = messageAttributes / stringValue.

Revision note. Written October 13, 2026, for every label that was there all along. "Ask, and it shall be given you," goes the old saying; with SQS attributes, that's the whole fix.

Related