Repository: localstack/localstack

Issue:
MessageAttributes can't be set on SQS message
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Current Behavior

When a new message is created with attributed, message: `{QueueUrl: http://localhost:32921/000000000000/c2e89862-2dd2-48a2-aba7-3948660eaea7,MessageBody: Test msg,DelaySeconds: 0,MessageAttributes: {retryCount={StringValue: 3,StringListValues: [],BinaryListValues: [],DataType: NUMBER}, timeToConsume={StringValue: 0,StringListValues: [],BinaryListValues: [],DataType: NUMBER}},MessageSystemAttributes: {},}` 
it fails to get published in the queue with exception: `com.amazonaws.services.sqs.model.AmazonSQSException: null (Service: AmazonSQS; Status Code: 400; Error Code: 400 ; Request ID: BDFBJI2A19T0WXE9J2GSPA0GO7MGIEOYBTVCCW4L2Z1W1UK6CX8A; Proxy: null)`

**Note: It is published successfully when I remove all attributes**

### Expected Behavior

Publish successfully in the queue

### How are you starting LocalStack?

With the `localstack` script

### Steps To Reproduce

#### How are you starting localstack (e.g., `bin/localstack` command, arguments, or `docker-compose.yml`)

```
    private static final DockerImageName DOCKER_IMAGE_NAME = DockerImageName.parse("localstack/localstack");
    private static final String DEFAULT_TAG = "0.13.3";
    private final DockerImageName LOCAL_STACK_IMAGE = DOCKER_IMAGE_NAME.withTag(DEFAULT_TAG);

    localstack = new LocalStackContainer(LOCAL_STACK_IMAGE)
                .withServices(localStackService);

     localstack.start();
```

#### Client commands (e.g., AWS SDK code snippet, or sequence of "awslocal" commands)

#### Creating queue: 
```
public String createQueue(String queueName) {
        log.info("Creating queue with name: {}", queueName);
        try {
            CreateQueueRequest createQueueRequest = new CreateQueueRequest(queueName);
            return amazonSQS.createQueue(createQueueRequest).getQueueUrl();
        } catch (AmazonSQSException e) {
            log.error("Error While Creating Queue: {}", ExceptionUtils.getStackTrace(e));
            throw e;
        }
    }
```

Publish In Queue:
```
    public SendMessageResult publishInQueue(final String queueUrl, final PublishRequestMessage message) throws SQSPublisherException {
        log.info("Publishing Message: {}, in the queue: {}", message.getMessage(), queueUrl);
        try {
            final SendMessageRequest request = getSendMessageRequest(queueUrl, message.getMessage(), message.getMessageProperties());
            return amazonSQS.sendMessage(request);
        } catch (Exception ex) {
            throw new SQSPublisherException("Exception while queuing message: " + message + " ,exception: " + ex);
        }
    }

    private SendMessageRequest getSendMessageRequest(final String queueUrl,
                                                     final String message,
                                                     MessageProperties messageProperties) throws SQSPublisherException {

        messageProperties = handleEmptyProperties(messageProperties);

        Long timeToConsume = messageProperties.getTimeToConsumeMessage();
        Integer deliveryDelay = messageProperties.getDeliveryDelay();
        Integer retryCount = messageProperties.getRetryCount();

        try {
            final SendMessageRequest request = new SendMessageRequest(queueUrl, message)
                    .withMessageAttributes(Collections.unmodifiableMap(
                            getAttributeValueMap(Objects.nonNull(timeToConsume) ? timeToConsume : TIME_TO_CONSUME,
                                    Objects.nonNull(retryCount) ? retryCount : RETRY_COUNT)
                    ));

            int delay = getAllowedDelay(Objects.nonNull(deliveryDelay) ? deliveryDelay : DELIVERY_DELAY);
            request.setDelaySeconds(delay);

            return request;
        } catch (Exception e) {
            throw new SQSPublisherException("Error While Creating Message Request" + e);
        }
    }

    private Map<String, MessageAttributeValue> getAttributeValueMap(final long timeToConsume, final long retryCount) {
        Map<String, MessageAttributeValue> attributeValueMap = new HashMap<>();

        attributeValueMap.put(MessageProperty.RETRY.getDescription(),
                getAttributeValue(SQSAttributeDataType.NUMBER, String.valueOf(retryCount)));
        attributeValueMap.put(MessageProperty.TIME_TO_CONSUME.getDescription(),
                getAttributeValue(SQSAttributeDataType.NUMBER, String.valueOf(timeToConsume)));

        return attributeValueMap;
    }
```


### Environment

```markdown
- OS: macOS Big Sur
- LocalStack: 0.13.3
```


### Anything else?

_No response_


Discussion:
Hi Abinav, @abhinavsharma629, we recently implemented a new SQS provider that's still experimental but could maybe solve your issue. You can enable the provider using by passing the environment variable `PROVIDER_OVERRIDE_SQS=asf`. 

Please give it a try to see whether that solves the issue! This provider will likely become the default in the near future.
@thrau I have created my SQS like:
```
this.amazonSQS = AmazonSQSClientBuilder
                        .standard()
                        .withEndpointConfiguration(this.getEndpointConfiguration())
                        .withCredentials(this.getCredentials())
                        .build();
```

I tried by using: 
```
      localstack = new LocalStackContainer(LOCAL_STACK_IMAGE)
                .withServices(localStackService).withEnv("PROVIDER_OVERRIDE_SQS", "asf");

        localstack.start();
```
but now for publishing message its giving: 
`com.amazonaws.SdkClientException: Unable to execute HTTP request: Connect to localhost:4566 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused (Connection refused)`

Can you please tell me how or where to pass this env variable?

> Hi Abinav, @abhinavsharma629, we recently implemented a new SQS provider that's still experimental but could maybe solve your issue. You can enable the provider using by passing the environment variable `PROVIDER_OVERRIDE_SQS=asf`.
> 
> Please give it a try to see whether that solves the issue! This provider will likely become the default in the near future.


@thrau I think there is some issue as the SQS queue is getting created at port: 4566(exposed ports only), while SQS is running on port 32953, so that's why the connection is getting refused due to that while publishing message.

Getting endpoint configs using: `this.localstack.getEndpointConfiguration(localStackService)`


**NOTE**: It works correctly when I remove the env variable: `PROVIDER_OVERRIDE_SQS=asf`
The same is happening with tagging queue after the creation.
```
    return amazonSQS.tagQueue(queueUrl, new HashMap<String, String>() {{
                put(tagName, tagValue);
     }});
```
I've also run into this issue. I tested on a wide range of versions from 0.12.6 - 0.14.1 and it seems to be a persistent issue. This is very easy to reproduce using the AWS CLI:

```
aws --endpoint-url=http://localhost:4566 sqs create-queue \
  --queue-name localstack-test

aws --endpoint-url=http://localhost:4566 sqs send-message \
  --queue-url http://localhost:4566/000000000000/localstack-test \
  --message-body '{ "message": "body }' \
  --message-attributes 'Hello={DataType=String, StringValue="World"}'

aws --endpoint-url=http://localhost:4566 sqs receive-message \
  --queue-url http://localhost:4566/000000000000/localstack-test \
  --message-attribute-names ".*"
```

The final command above will return the payload missing the attributes:

```
{
    "Messages": [
        {
            "MessageId": "15ee0dac-9295-0dd8-5e31-c2f60ef3004b",
            "ReceiptHandle": "vgehcvkebddxcsycntlxbbegjoaejrhgxzckhkstfohblkgkjvlgllelklmifedhsfxfnvgyqsnmabemjhhmzggxmmviwfkcpzvxgtjyccaqtnjauiphrontjpoezjjfbzenpstmifylhzukumoeognizwevrafrtcmuaykbtvtlkosloqnpeikux",
            "MD5OfBody": "ff216adc307f74c1fa8b2c67d792e62c",
            "Body": "{ \"message\": \"body }"
        }
    ]
}
```

If you perform these same three commands using AWS (no localstack endpoint). It will work as expected.
Hi Wesley @workmanw, could you give our new SQS provider a try? You can activate it by running localstack with the environment variable `PROVIDER_OVERRIDE_SQS=asf`.
With the latest version and the `asf` SQS provider, I get the following response on the last command:
```
 % aws --endpoint-url=http://localhost:4566 sqs receive-message \
  --queue-url http://localhost:4566/000000000000/localstack-test \
  --message-attribute-names ".*"
```
```json
{
    "Messages": [
        {
            "MessageId": "2457d574-5b97-4f11-9dc7-e2ebf09e3ba6",
            "ReceiptHandle": "SBFXkXY8Tkg7eEmKmyFc6p9KMw1upvFfIU4GIN0HfRFDOCpRSI6fPr2YO7wWC2rc1y6IbUSB9w0Nvte6ljZUSk3YqzolGNYRitEUe1RQznSFnI74mM46AY0DZ9WfZGPvRDU0ge2YWsNV92NPtE1LEMiiZ9hxhUmOL88lZrzA5jgQ=",
            "MD5OfBody": "ff216adc307f74c1fa8b2c67d792e62c",
            "Body": "{ \"message\": \"body }",
            "Attributes": {
                "SenderId": "000000000000",
                "SentTimestamp": "1647907259"
            },
            "MD5OfMessageAttributes": "d41d8cd98f00b204e9800998ecf8427e"
        }
    ]
}
```
@thrau Sure. What version of localstack would this be available in? Also, FWIW, your snippet includes `Attributes` which are from the system, where as I'm trying to use MessageAttributes. 
@thrau Unfortunately it appears broken on 0.14.1. I can see the system `Attributes` are now coming back, but the user attribute `MessageAttributes` are not.

```
$ aws --endpoint-url=http://localhost:4566 sqs create-queue \                                                       ✔  19:06:44
  --queue-name localstack-test

{
    "QueueUrl": "http://localhost:4566/000000000000/localstack-test"
}


$ aws --endpoint-url=http://localhost:4566 sqs send-message \
  --queue-url http://localhost:4566/000000000000/localstack-test \
  --message-body '{ "message": "body }' \
  --message-attributes 'Hello={DataType=String, StringValue="World"}'

{
    "MD5OfMessageBody": "ff216adc307f74c1fa8b2c67d792e62c",
    "MD5OfMessageAttributes": "7db7fb1ba7f41366a8ea5eb18220b26d",
    "MessageId": "5f8e54b3-fa05-490b-906e-55aaf5c951a9"
}


$ aws --endpoint-url=http://localhost:4566 sqs receive-message \
  --queue-url http://localhost:4566/000000000000/localstack-test \
  --message-attribute-names ".*"

{
    "Messages": [
        {
            "MessageId": "5f8e54b3-fa05-490b-906e-55aaf5c951a9",
            "ReceiptHandle": "yZZ7gGdvRifaagsmo3lNFBO02vHlq7OKIs7wai5tQojc4Acy70mCqG7UPm7tFVSHyuD1UnOav29zmeX4g9EIQGLR5RNoKQnrTVnVyqvvYEzOqmek8XhKxQ32QR1AQ4ATPZDPpDw7QwkDRs0wSEks0iu2XYu8xN7qMeBTwBdwieLH=",
            "MD5OfBody": "ff216adc307f74c1fa8b2c67d792e62c",
            "Body": "{ \"message\": \"body }",
            "Attributes": {
                "SenderId": "000000000000",
                "SentTimestamp": "1647907520"
            },
            "MD5OfMessageAttributes": "d41d8cd98f00b204e9800998ecf8427e"
        }
    ]
}
```
> @thrau Sure. What version of localstack would this be available in? Also, FWIW, your snippet includes `Attributes` which are from the system, where as I'm trying to use MessageAttributes.

yes, fair point. completely missed that, although it's quite obvious now that you pointed it out.

Well, at least I can reproduce the issue ;-) I'll forward it to the team!
@thrau Awesome. Thanks a ton! If you point the `aws` cli at a real AWS region, you'll see the example works as expect. Here is a snippet of that:

```
{
    "Messages": [
        {
            "MessageId": "131cd2ea-ccf3-4c56-aa6c-3829786cb31f",
            "ReceiptHandle": "AQEBSBs4e48VeG8+YL+salXdwC/n4JvggILwm6o0DqoBnGq6/JNCqZ4iA+H/6HjO74rbWDYK4CA/6A5cNqSyfO3Zq/ncfg8GiRp8GgtDHCVSCnpcbH7HwZ2sBYTEk0zodbSnbPqbgGHLb/g32C/wYe3ikiNVn9eH3U4mWgIgtfY5AoBx0mJ4/lgDc/zISAXwZQYAJXorS4BFnSOE3tzNRdqDk8yPfyvNInOd47dVK74Y7+7VqVA0WeaMlaKiEXEqcF+jeURcpsneXuGhU9tJCmgrAKMtjJ6vwoXidZDL+TiaZGN97z0NLArux/SmCcCKqgCv8TYzfAiiAMsTdzWkB1WLNbSc/jn6dYZ30rNtdJtgEayyftY9fW29S2drwJ+nZAPAcNTehlOAKWq8Fk1Ddb1ZXw==",
            "MD5OfBody": "99914b932bd37a50b983c5e7c90ae93b",
            "Body": "{}",
            "MD5OfMessageAttributes": "7db7fb1ba7f41366a8ea5eb18220b26d",
            "MessageAttributes": {
                "Hello": {
                    "StringValue": "World",
                    "DataType": "String"
                }
            }
        }
    ]
}
```

Resolving patch:
diff --git a/localstack/services/sqs/provider.py b/localstack/services/sqs/provider.py
--- a/localstack/services/sqs/provider.py
+++ b/localstack/services/sqs/provider.py
@@ -403,10 +403,15 @@ def get(self, block=True, timeout=None, visibility_timeout: int = None) -> SqsMe
                 else:
                     self.inflight.add(standard_message)
 
-                # prepare message for receiver
-                # TODO: update message attributes (ApproximateFirstReceiveTimestamp, ApproximateReceiveCount)
-                copied_message = copy.deepcopy(standard_message)
-                copied_message.message["ReceiptHandle"] = receipt_handle
+            # prepare message for receiver
+            copied_message = copy.deepcopy(standard_message)
+            copied_message.message["Attributes"][
+                MessageSystemAttributeName.ApproximateReceiveCount
+            ] = str(standard_message.receive_times)
+            copied_message.message["Attributes"][
+                MessageSystemAttributeName.ApproximateFirstReceiveTimestamp
+            ] = standard_message.first_received
+            copied_message.message["ReceiptHandle"] = receipt_handle
 
             return copied_message
 
@@ -978,8 +983,6 @@ def _put_message(
         message_deduplication_id: String = None,
         message_group_id: String = None,
     ) -> Message:
-        # TODO: default message attributes (SenderId, ApproximateFirstReceiveTimestamp, ...)
-
         check_message_content(message_body)
         check_attributes(message_attributes)
         check_attributes(message_system_attributes)
@@ -1048,20 +1051,18 @@ def receive_message(
                 moved_to_dlq = self._dead_letter_check(queue, standard_message, context)
             if moved_to_dlq:
                 continue
-            # filter attributes
-            if message_attribute_names:
-                if "All" not in message_attribute_names:
-                    msg["MessageAttributes"] = {
-                        k: v
-                        for k, v in msg["MessageAttributes"].items()
-                        if k in message_attribute_names
-                    }
-                # TODO: why is this called even if we receive "All" attributes?
+
+            msg = copy.deepcopy(msg)
+            message_filter_attributes(msg, attribute_names)
+            message_filter_message_attributes(msg, message_attribute_names)
+
+            if msg.get("MessageAttributes"):
                 msg["MD5OfMessageAttributes"] = _create_message_attribute_hash(
                     msg["MessageAttributes"]
                 )
             else:
-                del msg["MessageAttributes"]
+                # delete the value that was computed when creating the message
+                msg.pop("MD5OfMessageAttributes")
 
             # add message to result
             messages.append(msg)
@@ -1180,7 +1181,8 @@ def set_queue_attributes(
                 valid_count = False
             if not valid_count:
                 raise InvalidParameterValue(
-                    f"Value {redrive_policy} for parameter RedrivePolicy is invalid. Reason: Invalid value for maxReceiveCount: {max_receive_count}, valid values are from 1 to 1000 both inclusive."
+                    f"Value {redrive_policy} for parameter RedrivePolicy is invalid. Reason: Invalid value for "
+                    f"maxReceiveCount: {max_receive_count}, valid values are from 1 to 1000 both inclusive. "
                 )
 
     def tag_queue(self, context: RequestContext, queue_url: String, tags: TagMap) -> None:
@@ -1232,7 +1234,7 @@ def _create_message_attributes(
         message_system_attributes: MessageBodySystemAttributeMap = None,
     ) -> Dict[MessageSystemAttributeName, str]:
         result: Dict[MessageSystemAttributeName, str] = {
-            MessageSystemAttributeName.SenderId: context.account_id,
+            MessageSystemAttributeName.SenderId: context.account_id,  # not the account ID in AWS
             MessageSystemAttributeName.SentTimestamp: str(now()),
         }
 
@@ -1329,3 +1331,66 @@ def resolve_queue_name(
             queue_name = get_queue_name_from_url(context.request.base_url)
 
     return queue_name
+
+
+def message_filter_attributes(message: Message, names: Optional[AttributeNameList]):
+    """
+    Utility function filter from the given message (in-place) the system attributes from the given list. It will
+    apply all rules according to:
+    https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sqs.html#SQS.Client.receive_message.
+
+    :param message: The message to filter (it will be modified)
+    :param names: the attributes names/filters
+    """
+    if "Attributes" not in message:
+        return
+
+    if not names:
+        del message["Attributes"]
+        return
+
+    if "All" in names:
+        return
+
+    for k in list(message["Attributes"].keys()):
+        if k not in names:
+            del message["Attributes"][k]
+
+
+def message_filter_message_attributes(message: Message, names: Optional[MessageAttributeNameList]):
+    """
+    Utility function filter from the given message (in-place) the message attributes from the given list. It will
+    apply all rules according to:
+    https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sqs.html#SQS.Client.receive_message.
+
+    :param message: The message to filter (it will be modified)
+    :param names: the attributes names/filters (can be 'All', '.*', or prefix filters like 'Foo.*')
+    """
+    if not message.get("MessageAttributes"):
+        return
+
+    if not names:
+        del message["MessageAttributes"]
+        return
+
+    if "All" in names or ".*" in names:
+        return
+
+    attributes = message["MessageAttributes"]
+    matched = []
+
+    keys = [name for name in names if ".*" not in name]
+    prefixes = [name.split(".*")[0] for name in names if ".*" in name]
+
+    # match prefix filters
+    for k in attributes:
+        if k in keys:
+            matched.append(k)
+            continue
+
+        for prefix in prefixes:
+            if k.startswith(prefix):
+                matched.append(k)
+            break
+
+    message["MessageAttributes"] = {k: attributes[k] for k in matched}


Test patch:
diff --git a/tests/integration/test_sqs.py b/tests/integration/test_sqs.py
--- a/tests/integration/test_sqs.py
+++ b/tests/integration/test_sqs.py
@@ -1328,8 +1328,7 @@ def test_get_list_queues_with_query_auth(self, aws_http_client_factory):
 
     @pytest.mark.aws_validated
     def test_system_attributes_have_no_effect_on_attr_md5(self, sqs_create_queue, sqs_client):
-        queue_name = f"queue-{short_uid()}"
-        queue_url = sqs_create_queue(QueueName=queue_name)
+        queue_url = sqs_create_queue()
 
         msg_attrs_provider = {"timestamp": {"StringValue": "1493147359900", "DataType": "Number"}}
         aws_trace_header = {
@@ -1735,6 +1734,104 @@ def test_sse_attributes_are_accepted(self, sqs_client, sqs_create_queue):
             assert k in keys
             assert attributes[k] == result_attributes[k]
 
+    def test_receive_message_message_attribute_names_filters(
+        self, sqs_client, sqs_create_queue, snapshot
+    ):
+        """
+        Receive message allows a list of filters to be passed with MessageAttributeNames. See:
+        https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sqs.html#SQS.Client.receive_message
+        """
+        snapshot.skip_key(re.compile(r"^ReceiptHandle$"), "")
+
+        queue_url = sqs_create_queue(Attributes={"VisibilityTimeout": "0"})
+
+        response = sqs_client.send_message(
+            QueueUrl=queue_url,
+            MessageBody="msg",
+            MessageAttributes={
+                "Help.Me": {"DataType": "String", "StringValue": "Me"},
+                "Hello": {"DataType": "String", "StringValue": "There"},
+                "General": {"DataType": "String", "StringValue": "Kenobi"},
+            },
+        )
+        assert snapshot.match("send_message_response", response)
+
+        def receive_message(message_attribute_names):
+            return sqs_client.receive_message(
+                QueueUrl=queue_url,
+                WaitTimeSeconds=5,
+                MessageAttributeNames=message_attribute_names,
+            )
+
+        # test empty filter
+        response = receive_message([])
+        # do the first check with the entire response
+        assert snapshot.match("empty_filter", response)
+
+        # test "All"
+        response = receive_message(["All"])
+        assert snapshot.match("all_name", response)
+
+        # test ".*"
+        response = receive_message([".*"])
+        assert snapshot.match("all_wildcard", response["Messages"][0])
+
+        # test only non-existent names
+        response = receive_message(["Foo", "Help"])
+        assert snapshot.match("only_non_existing_names", response["Messages"][0])
+
+        # test all existing
+        response = receive_message(["Hello", "General"])
+        assert snapshot.match("only_existing", response["Messages"][0])
+
+        # test existing and non-existing
+        response = receive_message(["Foo", "Hello"])
+        assert snapshot.match("existing_and_non_existing", response["Messages"][0])
+
+        # test prefix filters
+        response = receive_message(["Hel.*"])
+        assert snapshot.match("prefix_filter", response["Messages"][0])
+
+        # test illegal names
+        response = receive_message(["AWS."])
+        assert snapshot.match("illegal_name_1", response)
+        response = receive_message(["..foo"])
+        assert snapshot.match("illegal_name_2", response)
+
+    def test_receive_message_attribute_names_filters(self, sqs_client, sqs_create_queue, snapshot):
+        snapshot.skip_key(re.compile(r"^ReceiptHandle$"), "")
+        snapshot.skip_key(re.compile(r"^SenderId$"), "")
+
+        queue_url = sqs_create_queue(Attributes={"VisibilityTimeout": "0"})
+
+        sqs_client.send_message(
+            QueueUrl=queue_url,
+            MessageBody="msg",
+            MessageAttributes={
+                "Foo": {"DataType": "String", "StringValue": "Bar"},
+            },
+        )
+
+        def receive_message(attribute_names, message_attribute_names=None):
+            return sqs_client.receive_message(
+                QueueUrl=queue_url,
+                WaitTimeSeconds=5,
+                AttributeNames=attribute_names,
+                MessageAttributeNames=message_attribute_names or [],
+            )
+
+        response = receive_message(["All"])
+        assert snapshot.match("all_attributes", response)
+
+        response = receive_message(["All"], ["All"])
+        assert snapshot.match("all_system_and_message_attributes", response)
+
+        response = receive_message(["SenderId"])
+        assert snapshot.match("single_attribute", response)
+
+        response = receive_message(["SenderId", "SequenceNumber"])
+        assert snapshot.match("multiple_attributes", response)
+
 
 def get_region():
     return os.environ.get("AWS_DEFAULT_REGION") or TEST_REGION
diff --git a/tests/integration/test_sqs.snapshot.json b/tests/integration/test_sqs.snapshot.json
new file mode 100644
--- /dev/null
+++ b/tests/integration/test_sqs.snapshot.json
@@ -0,0 +1,214 @@
+{
+  "tests/integration/test_sqs.py::TestSqsProvider::test_receive_message_message_attribute_names_filters": {
+    "send_message_response": {
+      "MD5OfMessageBody": "6e2baaf3b97dbeef01c0043275f9a0e7",
+      "MD5OfMessageAttributes": "4c360f3fdafd970e05fae2f149d997f5",
+      "MessageId": "<uuid>",
+      "ResponseMetadata": "<response-metadata>"
+    },
+    "empty_filter": {
+      "Messages": [
+        {
+          "MessageId": "<uuid>",
+          "ReceiptHandle": "",
+          "MD5OfBody": "6e2baaf3b97dbeef01c0043275f9a0e7",
+          "Body": "msg"
+        }
+      ],
+      "ResponseMetadata": "<response-metadata>"
+    },
+    "all_name": {
+      "Messages": [
+        {
+          "MessageId": "<uuid>",
+          "ReceiptHandle": "",
+          "MD5OfBody": "6e2baaf3b97dbeef01c0043275f9a0e7",
+          "Body": "msg",
+          "MD5OfMessageAttributes": "4c360f3fdafd970e05fae2f149d997f5",
+          "MessageAttributes": {
+            "General": {
+              "StringValue": "Kenobi",
+              "DataType": "String"
+            },
+            "Hello": {
+              "StringValue": "There",
+              "DataType": "String"
+            },
+            "Help.Me": {
+              "StringValue": "Me",
+              "DataType": "String"
+            }
+          }
+        }
+      ],
+      "ResponseMetadata": "<response-metadata>"
+    },
+    "all_wildcard": {
+      "MessageId": "<uuid>",
+      "ReceiptHandle": "",
+      "MD5OfBody": "6e2baaf3b97dbeef01c0043275f9a0e7",
+      "Body": "msg",
+      "MD5OfMessageAttributes": "4c360f3fdafd970e05fae2f149d997f5",
+      "MessageAttributes": {
+        "General": {
+          "StringValue": "Kenobi",
+          "DataType": "String"
+        },
+        "Hello": {
+          "StringValue": "There",
+          "DataType": "String"
+        },
+        "Help.Me": {
+          "StringValue": "Me",
+          "DataType": "String"
+        }
+      }
+    },
+    "only_non_existing_names": {
+      "MessageId": "<uuid>",
+      "ReceiptHandle": "",
+      "MD5OfBody": "6e2baaf3b97dbeef01c0043275f9a0e7",
+      "Body": "msg"
+    },
+    "only_existing": {
+      "MessageId": "<uuid>",
+      "ReceiptHandle": "",
+      "MD5OfBody": "6e2baaf3b97dbeef01c0043275f9a0e7",
+      "Body": "msg",
+      "MD5OfMessageAttributes": "fca026605781cb4126a1e9044df24232",
+      "MessageAttributes": {
+        "General": {
+          "StringValue": "Kenobi",
+          "DataType": "String"
+        },
+        "Hello": {
+          "StringValue": "There",
+          "DataType": "String"
+        }
+      }
+    },
+    "existing_and_non_existing": {
+      "MessageId": "<uuid>",
+      "ReceiptHandle": "",
+      "MD5OfBody": "6e2baaf3b97dbeef01c0043275f9a0e7",
+      "Body": "msg",
+      "MD5OfMessageAttributes": "a311262e065454b75da111d535b8dacd",
+      "MessageAttributes": {
+        "Hello": {
+          "StringValue": "There",
+          "DataType": "String"
+        }
+      }
+    },
+    "prefix_filter": {
+      "MessageId": "<uuid>",
+      "ReceiptHandle": "",
+      "MD5OfBody": "6e2baaf3b97dbeef01c0043275f9a0e7",
+      "Body": "msg",
+      "MD5OfMessageAttributes": "83fee93c1bcd8b9a5a923ffacdc636c7",
+      "MessageAttributes": {
+        "Hello": {
+          "StringValue": "There",
+          "DataType": "String"
+        },
+        "Help.Me": {
+          "StringValue": "Me",
+          "DataType": "String"
+        }
+      }
+    },
+    "illegal_name_1": {
+      "Messages": [
+        {
+          "MessageId": "<uuid>",
+          "ReceiptHandle": "",
+          "MD5OfBody": "6e2baaf3b97dbeef01c0043275f9a0e7",
+          "Body": "msg"
+        }
+      ],
+      "ResponseMetadata": "<response-metadata>"
+    },
+    "illegal_name_2": {
+      "Messages": [
+        {
+          "MessageId": "<uuid>",
+          "ReceiptHandle": "",
+          "MD5OfBody": "6e2baaf3b97dbeef01c0043275f9a0e7",
+          "Body": "msg"
+        }
+      ],
+      "ResponseMetadata": "<response-metadata>"
+    }
+  },
+  "tests/integration/test_sqs.py::TestSqsProvider::test_receive_message_attribute_names_filters": {
+    "all_attributes": {
+      "Messages": [
+        {
+          "MessageId": "<uuid>",
+          "ReceiptHandle": "",
+          "MD5OfBody": "6e2baaf3b97dbeef01c0043275f9a0e7",
+          "Body": "msg",
+          "Attributes": {
+            "SenderId": "",
+            "ApproximateFirstReceiveTimestamp": "<timestamp>",
+            "ApproximateReceiveCount": "1",
+            "SentTimestamp": "<timestamp>"
+          }
+        }
+      ],
+      "ResponseMetadata": "<response-metadata>"
+    },
+    "all_system_and_message_attributes": {
+      "Messages": [
+        {
+          "MessageId": "<uuid>",
+          "ReceiptHandle": "",
+          "MD5OfBody": "6e2baaf3b97dbeef01c0043275f9a0e7",
+          "Body": "msg",
+          "Attributes": {
+            "SenderId": "",
+            "ApproximateFirstReceiveTimestamp": "<timestamp>",
+            "ApproximateReceiveCount": "2",
+            "SentTimestamp": "<timestamp>"
+          },
+          "MD5OfMessageAttributes": "ae7155c6026991b6d54b11589678bf9c",
+          "MessageAttributes": {
+            "Foo": {
+              "StringValue": "Bar",
+              "DataType": "String"
+            }
+          }
+        }
+      ],
+      "ResponseMetadata": "<response-metadata>"
+    },
+    "single_attribute": {
+      "Messages": [
+        {
+          "MessageId": "<uuid>",
+          "ReceiptHandle": "",
+          "MD5OfBody": "6e2baaf3b97dbeef01c0043275f9a0e7",
+          "Body": "msg",
+          "Attributes": {
+            "SenderId": ""
+          }
+        }
+      ],
+      "ResponseMetadata": "<response-metadata>"
+    },
+    "multiple_attributes": {
+      "Messages": [
+        {
+          "MessageId": "<uuid>",
+          "ReceiptHandle": "",
+          "MD5OfBody": "6e2baaf3b97dbeef01c0043275f9a0e7",
+          "Body": "msg",
+          "Attributes": {
+            "SenderId": ""
+          }
+        }
+      ],
+      "ResponseMetadata": "<response-metadata>"
+    }
+  }
+}
diff --git a/tests/unit/aws/protocol/test_serializer.py b/tests/unit/aws/protocol/test_serializer.py
--- a/tests/unit/aws/protocol/test_serializer.py
+++ b/tests/unit/aws/protocol/test_serializer.py
@@ -347,6 +347,12 @@ def test_query_serializer_sqs_flattened_list_map_with_botocore():
                 "ReceiptHandle": "AQEBZ14sCjWJuot0T8G2Eg3S8C+sJGg+QRKYCJjfd8iiOsrPfUzbXSjlQquT9NZP1Mxxkcud3HcaxvS7I1gxoM9MSjbpenKgkti8TPCc7nQBUk9y6xXYWlhysjgAi9YjExUIxO2ozYZuwyksOvIxS4NZs2aBctyR74N3XjOO/t8GByAz2u7KR5vYJu418Y9apAuYB1n6ZZ6aE1NrjIK9mjGCKSqE3YxN5SNkKXf1zRwTUjq8cE73F7cK7DBXNFWBTZSYkLLnFg/QuqKh0dfwGgLseeKhHUxw2KiP9qH4kvXBn2UdeI8jkFMbPERiSf2KMrGKyMCtz3jL+YVRYkB4BB0hx15Brrgo/zhePXHbT692VxKF98MIMQc/v+dc6aewQZldjuq6ANrp4RM+LdjlTPg7ow==",
                 "MD5OfBody": "13c0c73bbf11056450c43bf3159b3585",
                 "Body": '{"foo": "bared"}',
+                "Attributes": {"SenderId": "000000000000", "SentTimestamp": "1652963711"},
+                "MD5OfMessageAttributes": "fca026605781cb4126a1e9044df24232",
+                "MessageAttributes": {
+                    "Hello": {"StringValue": "There", "DataType": "String"},
+                    "General": {"StringValue": "Kenobi", "DataType": "String"},
+                },
             }
         ]
     }