Amazon SQS Message Queue Setup
Amazon SQS is a managed message queue service from AWS without need to administer a broker. Two types: Standard Queue (high throughput, possible duplicates) and FIFO Queue (guaranteed order, exactly once).
Creating queue via Terraform
# Dead Letter Queue
resource "aws_sqs_queue" "dlq" {
name = "myapp-jobs-dlq"
message_retention_seconds = 1209600 # 14 days
}
# Main queue
resource "aws_sqs_queue" "jobs" {
name = "myapp-jobs"
visibility_timeout_seconds = 300 # message processing time
message_retention_seconds = 86400 # 24 hours
receive_wait_time_seconds = 20 # long polling
redrive_policy = jsonencode({
deadLetterTargetArn = aws_sqs_queue.dlq.arn
maxReceiveCount = 3 # 3 failures → DLQ
})
}
# FIFO queue (for ordered tasks)
resource "aws_sqs_queue" "orders_fifo" {
name = "myapp-orders.fifo"
fifo_queue = true
content_based_deduplication = true
deduplication_scope = "messageGroup"
fifo_throughput_limit = "perMessageGroupId"
}
# IAM policy for application
resource "aws_iam_policy" "sqs_app" {
name = "myapp-sqs-access"
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = [
"sqs:SendMessage",
"sqs:ReceiveMessage",
"sqs:DeleteMessage",
"sqs:GetQueueAttributes",
]
Resource = [aws_sqs_queue.jobs.arn, aws_sqs_queue.dlq.arn]
}]
})
}
PHP: AWS SDK
use Aws\Sqs\SqsClient;
class SqsQueue
{
private SqsClient $client;
private string $queueUrl;
public function __construct()
{
$this->client = new SqsClient([
'version' => 'latest',
'region' => config('aws.region', 'eu-west-1'),
]);
$this->queueUrl = config('queue.connections.sqs.queue');
}
public function send(string $jobClass, array $payload, int $delaySeconds = 0): string
{
$result = $this->client->sendMessage([
'QueueUrl' => $this->queueUrl,
'MessageBody' => json_encode([
'job' => $jobClass,
'payload' => $payload,
'attempts' => 0,
'sent_at' => now()->toIso8601String(),
]),
'DelaySeconds' => $delaySeconds,
'MessageAttributes' => [
'JobClass' => [
'DataType' => 'String',
'StringValue' => $jobClass,
],
],
]);
return $result['MessageId'];
}
public function poll(int $maxMessages = 10): void
{
$result = $this->client->receiveMessage([
'QueueUrl' => $this->queueUrl,
'MaxNumberOfMessages' => $maxMessages,
'WaitTimeSeconds' => 20, // long polling
'VisibilityTimeout' => 300, // 5 minutes for processing
]);
foreach ($result->get('Messages') ?? [] as $message) {
$this->processMessage($message);
}
}
private function processMessage(array $message): void
{
try {
$body = json_decode($message['Body'], true);
$job = app($body['job']);
$job->handle($body['payload']);
$this->client->deleteMessage([
'QueueUrl' => $this->queueUrl,
'ReceiptHandle' => $message['ReceiptHandle'],
]);
} catch (\Throwable $e) {
Log::error('SQS job failed', [
'job' => $body['job'] ?? 'unknown',
'error' => $e->getMessage(),
]);
// Message returns to queue after VisibilityTimeout expires
}
}
}
Laravel Queue with SQS
Laravel supports SQS out of the box:
QUEUE_CONNECTION=sqs
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=secret
AWS_DEFAULT_REGION=eu-west-1
SQS_QUEUE=https://sqs.eu-west-1.amazonaws.com/123456789/myapp-jobs
// Standard Laravel Job
class ProcessOrderJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int $tries = 3;
public int $timeout = 120;
public function __construct(private int $orderId) {}
public function handle(OrderService $service): void
{
$service->process($this->orderId);
}
public function failed(\Throwable $e): void
{
Log::error('Order processing failed', [
'order_id' => $this->orderId,
'error' => $e->getMessage(),
]);
}
}
// Publishing
ProcessOrderJob::dispatch($order->id);
// With 5 minute delay
ProcessOrderJob::dispatch($order->id)->delay(now()->addMinutes(5));
SQS + Lambda (serverless processing)
# Trigger Lambda from SQS
resource "aws_lambda_event_source_mapping" "sqs_lambda" {
event_source_arn = aws_sqs_queue.jobs.arn
function_name = aws_lambda_function.worker.arn
batch_size = 10
maximum_batching_window_in_seconds = 5
function_response_types = ["ReportBatchItemFailures"]
}
# Lambda handler (Python)
import json
def handler(event, context):
failed_ids = []
for record in event['Records']:
try:
body = json.loads(record['body'])
process_job(body)
except Exception as e:
print(f"Failed: {record['messageId']}: {e}")
failed_ids.append({'itemIdentifier': record['messageId']})
# Partial batch failure — only failed items return to queue
return {'batchItemFailures': [{'itemIdentifier': id} for id in failed_ids]}
Monitoring
CloudWatch SQS metrics: ApproximateNumberOfMessagesVisible (queue size), NumberOfMessagesSent, NumberOfMessagesDeleted, ApproximateAgeOfOldestMessage.
resource "aws_cloudwatch_metric_alarm" "sqs_depth" {
alarm_name = "sqs-queue-depth"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 2
metric_name = "ApproximateNumberOfMessagesVisible"
namespace = "AWS/SQS"
period = 60
statistic = "Maximum"
threshold = 1000
alarm_actions = [aws_sns_topic.alerts.arn]
dimensions = { QueueName = aws_sqs_queue.jobs.name }
}
Implementation timeline
Laravel Queue + SQS: 1 day (driver is built-in). Custom PHP/Node.js consumer with DLQ and monitoring: 2–3 days. SQS + Lambda serverless: 2–3 days.







