Amazon SQS

The AWS SDKs and the AWS CLI, with a custom endpoint. Standard and FIFO queues, dead-letter queues and message move tasks.

Endpoint and credentials

Endpointhttps://sqs.powerqueue.link
Access key idyour tenant identifier: <your-identifier>
Secret access keyanything (signatures are not checked, but SDKs insist on one)
Regionanything; it appears in queue ARNs
Queue URLshttps://sqs.powerqueue.link/<your-identifier>/<queue-name>
Queue ARNsarn:aws:sqs:<region>:<your-identifier>:<queue-name>

The identifier takes the place of the AWS account id in URLs and ARNs. Recent AWS SDKs and the AWS CLI also read the endpoint from the environment variable AWS_ENDPOINT_URL_SQS, so existing code can be pointed here without changes:

export AWS_ENDPOINT_URL_SQS=https://sqs.powerqueue.link
export AWS_ACCESS_KEY_ID=<your-identifier>
export AWS_SECRET_ACCESS_KEY=anything
export AWS_DEFAULT_REGION=us-east-1

Python (boto3)

pip install boto3:

import boto3

sqs = boto3.client(
    "sqs",
    endpoint_url="https://sqs.powerqueue.link",
    region_name="us-east-1",
    aws_access_key_id="<your-identifier>",
    aws_secret_access_key="anything",
)

url = sqs.create_queue(QueueName="orders", Attributes={"VisibilityTimeout": "30"})["QueueUrl"]
sqs.send_message(QueueUrl=url, MessageBody='{"id": 1}')
sqs.send_message_batch(
    QueueUrl=url,
    Entries=[{"Id": str(i), "MessageBody": f'{{"id": {i}}}'} for i in range(2, 5)],
)

while True:
    messages = sqs.receive_message(QueueUrl=url, MaxNumberOfMessages=10, WaitTimeSeconds=5).get("Messages", [])
    if not messages:
        break
    for message in messages:
        print(message["Body"])
        sqs.delete_message(QueueUrl=url, ReceiptHandle=message["ReceiptHandle"])

JavaScript (AWS SDK v3)

npm install @aws-sdk/client-sqs, then run as an ES module:

import {
  SQSClient, GetQueueUrlCommand, SendMessageCommand, ReceiveMessageCommand, DeleteMessageCommand,
} from "@aws-sdk/client-sqs";

const sqs = new SQSClient({
  endpoint: "https://sqs.powerqueue.link",
  region: "us-east-1",
  credentials: { accessKeyId: "<your-identifier>", secretAccessKey: "anything" },
});

const { QueueUrl } = await sqs.send(new GetQueueUrlCommand({ QueueName: "orders" }));
await sqs.send(new SendMessageCommand({ QueueUrl, MessageBody: "hello" }));
const { Messages = [] } = await sqs.send(
  new ReceiveMessageCommand({ QueueUrl, MaxNumberOfMessages: 10, WaitTimeSeconds: 5 }),
);
for (const message of Messages) {
  console.log(message.Body);
  await sqs.send(new DeleteMessageCommand({ QueueUrl, ReceiptHandle: message.ReceiptHandle }));
}

Go (AWS SDK for Go v2)

go get github.com/aws/aws-sdk-go-v2/config github.com/aws/aws-sdk-go-v2/credentials github.com/aws/aws-sdk-go-v2/service/sqs:

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/credentials"
	"github.com/aws/aws-sdk-go-v2/service/sqs"
)

func main() {
	ctx := context.Background()
	cfg, err := config.LoadDefaultConfig(ctx,
		config.WithRegion("us-east-1"),
		config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider("<your-identifier>", "anything", "")),
	)
	if err != nil {
		log.Fatal(err)
	}
	client := sqs.NewFromConfig(cfg, func(o *sqs.Options) {
		o.BaseEndpoint = aws.String("https://sqs.powerqueue.link")
	})

	queue, err := client.GetQueueUrl(ctx, &sqs.GetQueueUrlInput{QueueName: aws.String("orders")})
	if err != nil {
		log.Fatal(err)
	}
	if _, err := client.SendMessage(ctx, &sqs.SendMessageInput{QueueUrl: queue.QueueUrl, MessageBody: aws.String("hello")}); err != nil {
		log.Fatal(err)
	}
	received, err := client.ReceiveMessage(ctx, &sqs.ReceiveMessageInput{
		QueueUrl: queue.QueueUrl, MaxNumberOfMessages: 10, WaitTimeSeconds: 5,
	})
	if err != nil {
		log.Fatal(err)
	}
	for _, message := range received.Messages {
		fmt.Println(*message.Body)
		if _, err := client.DeleteMessage(ctx, &sqs.DeleteMessageInput{QueueUrl: queue.QueueUrl, ReceiptHandle: message.ReceiptHandle}); err != nil {
			log.Fatal(err)
		}
	}
}

AWS CLI

With the four environment variables above set:

aws sqs create-queue --queue-name orders
aws sqs send-message --queue-url https://sqs.powerqueue.link/<your-identifier>/orders --message-body hello
aws sqs receive-message --queue-url https://sqs.powerqueue.link/<your-identifier>/orders --wait-time-seconds 5
aws sqs list-queues

Or pass --endpoint-url https://sqs.powerqueue.link on each command instead of AWS_ENDPOINT_URL_SQS.

Supported operations

All 23 SQS actions, over both the AWS JSON protocol (current SDKs) and the older Query protocol:

Differences from Amazon SQS

Messages from other interfaces

A body sent through another interface that is not valid SQS text arrives base64-encoded with the attribute x-pq-body-encoding = base64; a typed Service Bus body arrives as JSON with x-pq-body-encoding = json. To send binary for the other interfaces, send its base64 with x-pq-body-encoding = base64: the bytes are stored. AMQP 0-9-1 headers and Service Bus properties arrive as message attributes with their types; an AMQP table or array header as JSON of type String.json, a decimal as Number. Both digests (MD5OfMessageBody, MD5OfMessageAttributes) cover what you receive, markers included.

The full rules, with examples: Using one queue from several interfaces.

Errors

SituationError codeHTTP
Access key id is not a tenant identifier, or the request is unsignedInvalidClientTokenId, with the identifier help text403
Too many new tenants from your address, or the service is fullRequestThrottled403
More than 100 requests a second (after a burst of 200)RequestThrottled (botocore retries it as throttling)403
Body plus attributes over 256 KiBInvalidParameterValue400
A 101st queue, by CreateQueue or by useOverLimit403
Queue holding 1,000 messages, or a tenant holding 16 MiB of messagesOverLimit (a batch entry fails with OverLimit)403
Attributes, tags and policies of a tenant's queues, with its other settings, growing past 1 MiB togetherOverLimit403
MessageRetentionPeriod over 3,600 seconds, or MaximumMessageSize over 262,144 bytesInvalidAttributeValue400
Another tenant's queue URLQueueDoesNotExist400
Another tenant's ARN in a redrive policy or move taskInvalidParameterValue400