Docs / SQS

SQS

SQS is a custom Python reimplementation, not a thin wrapper. All 23 SQS operations are implemented in LocalEmu's own code: standard and FIFO queues, real visibility-timeout tracking, real long polling, dead-letter queues with redrive, message move tasks, CloudWatch metric emission, queue policies, server-side encryption metadata, tags, and persistence. Receiving a message blocks the request thread until a message arrives or the wait timer expires, just like real AWS.

Operation-level coverage: see the SQS coverage matrix.

Quick start

Terminal
$ awsemu sqs create-queue --queue-name jobs
{
  "QueueUrl": "http://sqs.us-east-1.localhost:4566/000000000000/jobs"
}

$ awsemu sqs send-message \
    --queue-url http://sqs.us-east-1.localhost:4566/000000000000/jobs \
    --message-body '{"task":"resize","file":"img.png"}'

$ awsemu sqs receive-message \
    --queue-url http://sqs.us-east-1.localhost:4566/000000000000/jobs \
    --max-number-of-messages 1 \
    --wait-time-seconds 5
{
  "Messages": [{
    "MessageId":     "a1b2c3...",
    "ReceiptHandle": "AQEB...",
    "Body":          "{\"task\":\"resize\",\"file\":\"img.png\"}",
    "Attributes": {
      "ApproximateReceiveCount":          "1",
      "ApproximateFirstReceiveTimestamp": "1747700000000",
      "SentTimestamp":                    "1747699999500",
      "SenderId":                         "AKIAIOSFODNN7EXAMPLE"
    }
  }]
}

$ awsemu sqs delete-message \
    --queue-url http://sqs.us-east-1.localhost:4566/000000000000/jobs \
    --receipt-handle "AQEB..."

The queue URL returned by create-queue is the standard-format URL (sqs.us-east-1.localhost:4566/<account>/<queue>). Override the URL shape with SQS_ENDPOINT_STRATEGY if you need the domain, path, or dynamic format.

Architecture

SQS lives at services/sqs/. The provider class SqsProvider (provider.py:652) owns four background threads:

Queue storage differs by type:

When PERSISTENCE=1 is set, the SqsStore (queues + in-flight messages + delayed messages + dedup state) is serialised and restored across restarts via the standard StateVisitor hook (provider.py:684-685).

Configuration

VariableDefaultPurpose
SQS_ENDPOINT_STRATEGYstandardShape of the queue URL returned by CreateQueue and GetQueueUrl. Options: standard (sqs.<region>.<host>/<acct>/<q>), domain ([<region>.]queue.<host>/<acct>/<q>), path (<host>/queue/<region>/<acct>/<q>), dynamic, off.
SQS_ENABLE_MESSAGE_RETENTION_PERIODfalseWhen true, the QueueUpdateWorker deletes messages whose age exceeds MessageRetentionPeriod. Off by default: messages stay in the queue forever until they are received and deleted.
SQS_DELAY_PURGE_RETRYfalseWhen true, calling PurgeQueue within 60 seconds of a prior purge raises PurgeQueueInProgress as real AWS does.
SQS_DELAY_RECENTLY_DELETEDfalseWhen true, re-creating a queue within 60 seconds of deleting it raises QueueDeletedRecently as real AWS does.
SQS_DISABLE_MAX_NUMBER_OF_MESSAGE_LIMITfalseWhen true, ReceiveMessage --max-number-of-messages -1 drains every visible message in one call. Useful for test fixtures.
SQS_DISABLE_CLOUDWATCH_METRICSfalseWhen true, the CloudWatch metrics worker stops publishing.
SQS_CLOUDWATCH_METRICS_REPORT_INTERVAL60Seconds between CloudWatch metric publications.

Features supported

FeatureNotes
Queue lifecycleCreateQueue, GetQueueUrl, ListQueues, DeleteQueue, GetQueueAttributes, SetQueueAttributes.
Standard and FIFO queuesFIFO queues must end in .fifo and require FifoQueue=true.
Send and receiveSendMessage, SendMessageBatch, ReceiveMessage, DeleteMessage, DeleteMessageBatch.
Visibility timeoutChangeMessageVisibility + ChangeMessageVisibilityBatch. Default 30 s, set per-queue or per-receive. The background worker requeues expired in-flight messages.
Long pollingWaitTimeSeconds 0-20 on ReceiveMessage (enforced at provider.py:1166) and as the per-queue ReceiveMessageWaitTimeSeconds default.
Message attributesTypes String, Number, Binary. Name regex enforced at provider.py:594-640.
System attributesSenderId, SentTimestamp, ApproximateReceiveCount, ApproximateFirstReceiveTimestamp, AWSTraceHeader, DeadLetterQueueSourceArn.
Delay secondsPer-queue DelaySeconds attribute and per-message DelaySeconds override.
Dead-letter queuesRedrivePolicy with deadLetterTargetArn and maxReceiveCount (1-1000). Triggered automatically when the receive-count threshold is exceeded on the source queue.
Message move tasksStartMessageMoveTask, CancelMessageMoveTask, ListMessageMoveTasks for DLQ redrive automation back to the original source queue.
TagsTagQueue, UntagQueue, ListQueueTags.
Queue policiesAddPermission, RemovePermission. Policies are stored and returned by GetQueueAttributes but not used for access control on incoming requests.
SSE-SQS (managed encryption)SqsManagedSseEnabled attribute, defaults to true. Metadata only; bodies are stored in cleartext at rest.
PurgeQueueDrops every visible and in-flight message. With SQS_DELAY_PURGE_RETRY=true, a 60-second cooldown blocks a second purge.
ListDeadLetterSourceQueuesReturns the queues whose RedrivePolicy points at a given DLQ.
PersistenceAll queue state, including in-flight messages with their receipt handles, is restored across LocalEmu restarts when PERSISTENCE=1.

FIFO queues

FIFO queues add ordering guarantees and exactly-once delivery within a deduplication window:

Terminal
$ awsemu sqs create-queue --queue-name orders.fifo \
    --attributes FifoQueue=true,ContentBasedDeduplication=true

# Three messages, same MessageGroupId => strict order preserved on receive
$ for i in 1 2 3; do
    awsemu sqs send-message \
        --queue-url http://sqs.us-east-1.localhost:4566/000000000000/orders.fifo \
        --message-group-id customer-42 \
        --message-body "{\"order\": $i}"
  done

$ for i in 1 2 3; do
    awsemu sqs receive-message \
        --queue-url http://sqs.us-east-1.localhost:4566/000000000000/orders.fifo \
        --query 'Messages[0].Body' --output text
  done
{"order": 1}
{"order": 2}
{"order": 3}

Dead-letter queues and redrive

A redrive policy is attached as a queue attribute. Once a message is received and not deleted before its visibility timeout expires more times than maxReceiveCount, the next would-be redelivery sends it to the dead-letter queue instead. The DLQ receives the original message body plus a DeadLetterQueueSourceArn system attribute pointing at the source queue.

Terminal
# 1. Create the DLQ first; capture its ARN for the redrive policy
$ awsemu sqs create-queue --queue-name jobs-dlq
$ DLQ_ARN=$(awsemu sqs get-queue-attributes \
    --queue-url http://sqs.us-east-1.localhost:4566/000000000000/jobs-dlq \
    --attribute-names QueueArn --query 'Attributes.QueueArn' --output text)

# 2. Source queue with a redrive policy: 3 failed receives => DLQ
$ awsemu sqs create-queue --queue-name jobs \
    --attributes RedrivePolicy="{\"deadLetterTargetArn\":\"$DLQ_ARN\",\"maxReceiveCount\":\"3\"}"

$ Q=http://sqs.us-east-1.localhost:4566/000000000000/jobs
$ awsemu sqs send-message --queue-url $Q --message-body '{"work":"poisoned"}'

# Receive 3 times without deleting -- visibility timeout expires between each
# receive, so the next ReceiveMessage redelivers
$ for i in 1 2 3; do
    awsemu sqs receive-message --queue-url $Q --visibility-timeout 1 > /dev/null
    sleep 1.5
  done

# The 4th receive on the source queue returns nothing -- the message moved to the DLQ
$ awsemu sqs receive-message --queue-url $Q --query 'Messages' --output text
None

$ awsemu sqs receive-message \
    --queue-url http://sqs.us-east-1.localhost:4566/000000000000/jobs-dlq \
    --query 'Messages[0].Attributes.DeadLetterQueueSourceArn' --output text
arn:aws:sqs:us-east-1:000000000000:jobs

StartMessageMoveTask walks every message currently in a DLQ and re-sends each one to the source queue named in its DeadLetterQueueSourceArn. Use it to replay messages after fixing the consumer.

CloudWatch metrics

The metrics worker publishes seven metrics per queue every 60 seconds (configurable). Disable with SQS_DISABLE_CLOUDWATCH_METRICS=true. All metrics use the AWS/SQS namespace with a QueueName dimension.

MetricSource
NumberOfMessagesSentservices/sqs/provider.py:224-274
NumberOfMessagesDeletedservices/sqs/provider.py:276-288
NumberOfMessagesReceivedservices/sqs/provider.py:290-304
NumberOfEmptyReceivesservices/sqs/provider.py:305-310
ApproximateNumberOfMessagesVisibleservices/sqs/provider.py:344-346
ApproximateNumberOfMessagesNotVisibleservices/sqs/provider.py:348-353
ApproximateNumberOfMessagesDelayedservices/sqs/provider.py:355-361

Integration points

SQS is a sink for five other LocalEmu services. All five paths are exercised end-to-end:

SourceHow it lands on SQSCode path
S3 event notificationsS3 calls SendMessage with the standard Records envelope.services/s3/notifications.py:436,467
SNS subscriptionsSNS publishes deliver to subscribed queues via SendMessage (batched).services/sns/publisher.py
EventBridge targetsSqsTargetSender dispatches matched events.services/events/target.py
Lambda Event Source MappingThe SQS poller reads up to BatchSize messages and invokes the function once per batch.services/lambda_/event_source_mapping/pollers/sqs_poller.py
Pipes sourcePipes runs its own SQS poller and forwards each batch through the optional transform to the configured target.services/pipes/pipe_worker_factory.py
Terminal
# Wire S3 ObjectCreated events to an SQS queue
$ awsemu sqs create-queue --queue-name uploads-events
$ QUEUE_ARN=$(awsemu sqs get-queue-attributes \
    --queue-url http://sqs.us-east-1.localhost:4566/000000000000/uploads-events \
    --attribute-names QueueArn --query 'Attributes.QueueArn' --output text)

$ awsemu s3 mb s3://uploads
$ awsemu s3api put-bucket-notification-configuration --bucket uploads \
    --notification-configuration '{
      "QueueConfigurations": [{
        "QueueArn": "'"$QUEUE_ARN"'",
        "Events":   ["s3:ObjectCreated:*"]
      }]
    }'

$ echo "hello" | awsemu s3 cp - s3://uploads/hello.txt

$ awsemu sqs receive-message \
    --queue-url http://sqs.us-east-1.localhost:4566/000000000000/uploads-events \
    --query 'Messages[0].Body' --output text | python3 -m json.tool
{
  "Records": [{
    "eventSource": "aws:s3",
    "eventName":   "ObjectCreated:Put",
    "s3": {
      "bucket": {"name": "uploads"},
      "object": {"key": "hello.txt", "size": 6}
    }
  }]
}

Limits and defaults

LimitLocalEmuAWS SQSSource
Default visibility timeout30 s30 sservices/sqs/models.py:371
Default message retention4 days4 daysservices/sqs/models.py:368
Max long-poll wait20 s20 sservices/sqs/provider.py:1166
Max messages per ReceiveMessage1010services/sqs/provider.py:1184-1188
Queue name max length8080services/sqs/provider.py:136
maxReceiveCount range1-10001-1000services/sqs/provider.py:1355
FIFO dedup window5 min5 minservices/sqs/constants.py:14
Recently-deleted cooldown60 s (opt-in)60 sservices/sqs/constants.py:18
Default MaximumMessageSize1 MiB256 KiBservices/sqs/constants.py:21

LocalEmu's default MaximumMessageSize is 1 MiB, four times the 256 KiB AWS default. A queue created with no explicit MaximumMessageSize accepts messages up to 1 MiB. To match real AWS, pass --attributes MaximumMessageSize=262144 when calling CreateQueue. The other AWS-spec limits not listed above (max retention 14 days, max delay 15 min, max visibility 12 h, max in-flight 120 000 standard / 20 000 FIFO, max 10 message attributes per message) are not enforced by LocalEmu; the values are stored and reported as set but no upper bound is checked.

Known limitations