Learn LocalStack - SQS & SNS (Messaging)
Episode 8 of 23

Learn LocalStack - SQS & SNS (Messaging)

Building an asynchronous messaging system with SQS and SNS: creating queues and topics, sending and receiving messages, dead-letter queues, visibility timeout, and fan-out patterns between services.

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

Introduction

In episode 7 we brought Lambda to life as an event-driven compute unit. But in the real world, services don't call each other directly and wait for the answer — they talk through an intermediary called a message broker. AWS provides two services for this: SQS (a queue, one consumer per message) and SNS (a topic, one message broadcast to many subscribers). You could say: SQS is a bank queue, SNS is a mosque loudspeaker.

In this episode you'll create queues and topics, play with visibility timeout and dead-letter queues, then assemble a fan-out pattern connecting SQS, SNS, and Lambda in a single flow.

Queues & SQS Basics

Creating a Queue and Sending Messages

SQS is the perfect candidate for decoupling producer and consumer: the producer just drops a message, and the consumer processes it at its own pace. Let's start by creating a queue:

Buat queue, kirim, dan terima pesan
awslocal sqs create-queue --queue-name orders
awslocal sqs send-message \
  --queue-url http://localhost:4566/000000000000/orders \
  --message-body '{"orderId": "ORD-001"}'
awslocal sqs receive-message \
  --queue-url http://localhost:4566/000000000000/orders

Notice two important things:

  • A received message doesn't disappear automatically. It's only deleted after you call awslocal sqs delete-message with the --receipt-handle from the receive result. If you don't delete it, the message will appear again.
  • The queue endpoint uses the format http://localhost:4566/<account>/<queue-name>. This is the same address production code uses when accessing LocalStack.

Visibility Timeout

When a consumer receives a message, that message becomes invisible to other consumers for a period called the visibility timeout. This prevents two workers from processing the same message simultaneously. The default is 30 seconds:

Queue dengan visibility timeout khusus
awslocal sqs create-queue --queue-name slow-orders \
  --attributes VisibilityTimeout=120

If a worker fails and doesn't delete the message before the timeout expires, the message becomes visible again and can be retried by another worker. This pattern gives at-least-once semantics: it guarantees no message is lost, but some may be processed twice — so consumer idempotency is a requirement, not a choice.

Dead-Letter Queue

Imagine a corrupted message that always fails to process. Without a safety net, it would pollute the queue forever and block the line. The solution: a dead-letter queue (DLQ), a trash bin for messages that have been tried many times but still failed. Configure it via the RedrivePolicy attribute:

Setup DLQ dengan redrive policy
awslocal sqs create-queue --queue-name orders-dlq
awslocal sqs create-queue --queue-name orders \
  --attributes '{"RedrivePolicy":"{\"deadLetterTargetArn\":\"arn:aws:sqs:us-east-1:000000000000:orders-dlq\",\"maxReceiveCount\":3}"}'
awslocal sqs get-queue-attributes --queue-url http://localhost:4566/000000000000/orders \
  --attribute-names All

After three receives without a delete, the message is automatically moved to orders-dlq. This is where you can debug at your leisure: take the message from the DLQ, fix the consumer, then redrive it back.

Warning

The DLQ must be created before the main queue references it in RedrivePolicy. Referencing an ARN that doesn't exist yet causes queue creation to fail.

Topics & Fan-Out with SNS

SQS is a one-to-one queue. SNS is a one-to-many pattern: one publish spreads to every subscriber. Create a topic, register an SQS queue as a subscriber, then publish:

SNS topic dengan subscriber SQS
awslocal sns create-topic --name order-events
awslocal sns subscribe --topic-arn arn:aws:sns:us-east-1:000000000000:order-events \
  --protocol sqs \
  --notification-endpoint arn:aws:sqs:us-east-1:000000000000:orders
awslocal sns publish --topic-arn arn:aws:sns:us-east-1:000000000000:order-events \
  --message '{"status": "paid"}'
awslocal sqs receive-message --queue-url http://localhost:4566/000000000000/orders

This pattern is called fan-out: a single "order paid" event can simultaneously trigger record-keeping, email delivery, and inventory updates. Subscriptions can be SQS, Lambda, HTTP, or email (mocked). For email, the email protocol only writes the message to the emulator log — check it with localstack logs. For Lambda, point --notification-endpoint at the function ARN:

Subscribe Lambda ke SNS
awslocal lambda create-function --function-name order-handler \
  --runtime python3.12 --role arn:aws:iam::000000000000:role/lambda-role \
  --handler handler.handler --zip-file fileb://function.zip
awslocal sns subscribe --topic-arn arn:aws:sns:us-east-1:000000000000:order-events \
  --protocol lambda --notification-endpoint arn:aws:lambda:us-east-1:000000000000:function:order-handler

SQS → Lambda Event Source Mapping

When a queue needs to be processed automatically by Lambda, we use an event source mapping like in episode 7. LocalStack pulls messages from the queue and invokes the function in batches:

Event source mapping SQS ke Lambda
awslocal lambda create-event-source-mapping \
  --function-name order-handler \
  --event-source-arn arn:aws:sqs:us-east-1:000000000000:orders \
  --batch-size 10

This combination is the most common working pattern in production: SNS broadcasts the event → SQS absorbs the load → Lambda processes it batch by batch. LocalStack fully supports this chain locally.

Ordering & Latency Semantics

Choosing the right queue type matters. SQS has two types: Standard and FIFO:

AspectStandard QueueFIFO Queue
OrderingBest-effort, not guaranteedStrict, per message group
DeliveryAt-least-once (may duplicate)Exactly-once with dedup
ThroughputVery highLimited, capped per group
LatencyLowSlightly higher
Use casesLogs, metrics, notificationsFinancial transactions, event ordering

For guaranteed ordering, use FIFO. Its name must end in .fifo and every publish must include --message-group-id:

Queue FIFO dengan content-based dedup
awslocal sqs create-queue --queue-name orders.fifo \
  --attributes FifoQueue=true,ContentBasedDeduplication=true
awslocal sqs send-message \
  --queue-url http://localhost:4566/000000000000/orders.fifo \
  --message-body '{"event": "created"}' --message-group-id g1

Tip

Don't blindly pick FIFO for everything. FIFO limits throughput and adds complexity. Standard is faster and cheaper — use FIFO only when ordering or duplication truly can't be tolerated.

Closing

Summary of this episode:

  • SQS is a one-to-one queue: create-queue, send-message, receive-message, then delete-message with a receipt handle.
  • Visibility timeout prevents double processing; messages become visible again after the timeout expires.
  • A dead-letter queue holds failed messages based on maxReceiveCount in RedrivePolicy.
  • SNS broadcasts one message to many subscribers with the fan-out pattern, including to SQS and Lambda.
  • An SQS-to-Lambda event source mapping enables automatic per-batch processing.
  • Choose Standard for throughput, FIFO for strict ordering and exactly-once.

Messages now flow between your services. But who serves those messages to the outside world? In episode 9 we open the front door: API Gateway — REST and HTTP APIs, Lambda proxy integration, stages, custom domains, and API key and JWT authorizers. See you there!

Learn LocalStack - SQS & SNS (Messaging) | Learn LocalStack