SQS Queue with Dead Letter Queue and Access Policy
Scenario
Your application publishes job requests to an SQS queue and a worker process consumes them. Sometimes a message cannot be processed (corrupted data, temporary downstream failure). Instead of losing those messages, you want failed messages automatically moved to a Dead Letter Queue (DLQ) so they can be inspected and replayed later.
Your Objectives
- Create a Dead Letter Queue (aws_sqs_queue) with 4-day message retention.
- Create the main queue with a redrive policy pointing to the DLQ after maxReceiveCount = 3 failures.
- Enable server-side encryption on both queues using SqsManagedSseEnabled.
- Attach a queue policy to the main queue allowing the current AWS account to send messages.
- Output both queue URLs and ARNs.
Additional Context
The redrive_policy in SQS is a JSON string containing the DLQ ARN and the maxReceiveCount. After a message is received (but not deleted) 3 times, SQS automatically moves it to the DLQ. This prevents poison-pill messages from blocking the queue. Use jsonencode() in Terraform to build this JSON safely.
versions.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
}variables.tf
variable "aws_region" { type = string; default = "us-east-1" }
variable "queue_name" { type = string; default = "job-queue" }
variable "environment" { type = string; default = "dev" }
variable "max_receive_count"{ type = number; default = 3 }main.tf
# main.tf
provider "aws" {
region = var.aws_region
}
# Retrieve the current AWS account ID — needed for the queue policy.
data "aws_caller_identity" "current" {}
# Dead Letter Queue — receives messages that the main queue failed to process.
# Use a longer retention period so you have time to investigate failures.
resource "aws_sqs_queue" "dlq" {
name = "${var.queue_name}-dlq-${var.environment}"
message_retention_seconds = 345600 # 4 days (in seconds)
# Encrypt messages at rest (SQS-managed keys — no cost).
sqs_managed_sse_enabled = true
tags = { Name = "${var.queue_name}-dlq", Environment = var.environment, ManagedBy = "terraform" }
}
# Main queue — receives new job messages from producers.
resource "aws_sqs_queue" "main" {
name = "${var.queue_name}-${var.environment}"
message_retention_seconds = 86400 # 1 day
visibility_timeout_seconds = 30 # worker has 30s to process each message
sqs_managed_sse_enabled = true
# Redrive policy: after maxReceiveCount failed receive attempts, move to DLQ.
# jsonencode() creates valid JSON inline — no need for heredocs or raw strings.
redrive_policy = jsonencode({
deadLetterTargetArn = aws_sqs_queue.dlq.arn
maxReceiveCount = var.max_receive_count
})
tags = { Name = var.queue_name, Environment = var.environment, ManagedBy = "terraform" }
}
# Queue policy — IAM resource policy attached to the queue.
# This grants the AWS account's IAM principals the ability to use the queue.
data "aws_iam_policy_document" "queue_policy" {
statement {
sid = "AllowAccountAccess"
effect = "Allow"
principals {
type = "AWS"
identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"]
}
actions = [
"sqs:SendMessage",
"sqs:ReceiveMessage",
"sqs:DeleteMessage",
"sqs:GetQueueAttributes"
]
resources = [aws_sqs_queue.main.arn]
}
}
resource "aws_sqs_queue_policy" "main" {
queue_url = aws_sqs_queue.main.id
policy = data.aws_iam_policy_document.queue_policy.json
}outputs.tf
output "queue_url" { value = aws_sqs_queue.main.id }
output "queue_arn" { value = aws_sqs_queue.main.arn }
output "dlq_url" { value = aws_sqs_queue.dlq.id }
output "dlq_arn" { value = aws_sqs_queue.dlq.arn }Workflow Commands
terraform init
terraform apply
# Send a test message
aws sqs send-message \
--queue-url $(terraform output -raw queue_url) \
--message-body '{"job_id": "123", "type": "report"}'
# Receive and delete the message (simulating a successful worker)
aws sqs receive-message --queue-url $(terraform output -raw queue_url)
aws sqs delete-message \
--queue-url $(terraform output -raw queue_url) \
--receipt-handle PASTE_RECEIPT_HANDLE_HERE
terraform destroy
✓ Expected Output After Apply
Apply complete! Resources: 3 added, 0 changed, 0 destroyed. Outputs: dlq_arn = "arn:aws:sqs:us-east-1:123456789012:job-queue-dlq-dev" dlq_url = "https://sqs.us-east-1.amazonaws.com/123456789012/job-queue-dlq-dev" queue_arn = "arn:aws:sqs:us-east-1:123456789012:job-queue-dev" queue_url = "https://sqs.us-east-1.amazonaws.com/123456789012/job-queue-dev"

