AWS → STACKIT · MESSAGING GUIDE

Amazon SQS, SNS and MQ to STACKIT RabbitMQ

On STACKIT, queues, topics and brokers land on RabbitMQ: one instance hosts the queues of SQS and the exchanges of SNS, and an Amazon MQ for RabbitMQ broker gets an instance of its own. This guide follows a small messaging estate through a StackToIT run and shows what the Terraform draft carries, what your team recreates, and in which order the producers and consumers move.

Two queues, a topic and a broker: input and draft

A messaging estate written for this page and assessed by StackToIT on 4 November 2026: a standard queue with a dead-letter queue, a FIFO queue, an SNS topic with an SQS and an e-mail subscription, and an Amazon MQ RabbitMQ broker. Left: the SQS and SNS input. Right: the one resource block the run writes for all of it; the review comments above the block in the file are left out here.

Example inputmain.tf
resource "aws_sqs_queue" "orders_dlq" {
  name                      = "orders-dlq"
  message_retention_seconds = 1209600
}

resource "aws_sqs_queue" "orders" {
  name                       = "orders"
  visibility_timeout_seconds = 60
  message_retention_seconds  = 345600
  receive_wait_time_seconds  = 10
  redrive_policy = jsonencode({
    deadLetterTargetArn = aws_sqs_queue.orders_dlq.arn
    maxReceiveCount     = 5
  })
}

resource "aws_sqs_queue" "invoices" {
  name                        = "invoices.fifo"
  fifo_queue                  = true
  content_based_deduplication = true
}

resource "aws_sns_topic" "order_events" {
  name = "order-events"
}

resource "aws_sns_topic_subscription" "orders_to_queue" {
  topic_arn = aws_sns_topic.order_events.arn
  protocol  = "sqs"
  endpoint  = aws_sqs_queue.orders.arn
}

resource "aws_sns_topic_subscription" "orders_to_email" {
  topic_arn = aws_sns_topic.order_events.arn
  protocol  = "email"
  endpoint  = "ops@example.com"
}
STACKIT Terraform by StackToITterraform/messaging.tf
resource "stackit_rabbitmq_instance" "messaging" {
  project_id = var.project_id
  name       = "messaging"
  plan_name  = var.rabbitmq_plan_name
  version    = var.rabbitmq_version
  parameters = {
    sgw_acl = join(",", var.rabbitmq_allowed_cidrs)
  }
}
  • One instance for many queues

    The four SQS and SNS units are replaced by one stackit_rabbitmq_instance. Your team creates the queues and exchanges inside it, because they are made in the instance outside Terraform. Note that the draft writes a credential only for the broker's instance, not for this shared one: add a stackit_rabbitmq_credential for the queue clients before you plan.

  • The access list is yours to fill

    sgw_acl is built from a variable without a default. Left empty, STACKIT applies its default access list, so the run asks you to name the client networks before planning.

The migration in six steps

The order follows the run: its waves put the dead-letter queue before the queue that names it and the topic last, and its data-move document for the broker puts the structure first, the producers next and the draining consumers last.

  1. 1. Inventory every queue, topic and subscription

    List each queue with its type (standard or FIFO), visibility timeout, retention, redrive policy and the producers and consumers that use it. Do the same for each SNS topic and its subscription protocols. The run lists the units it found; the settings it does not carry are named per unit in the draft.

  2. 2. Decide the broker model

    Choose between RabbitMQ for everything and the opt-in PubSub API for queue-like objects. The run drafts RabbitMQ and writes the PubSub calls as JSON files you may use instead of it, never in addition to it.

  3. 3. Apply the instance and fill the access list

    Set project_id, the plan, the version and the client networks, then apply. Read the credential outputs the draft writes for the broker's instance (host, port, management URL, password) and add a credential to the shared instance yourself. Hand both to the clients before any cutover.

  4. 4. Recreate topology

    Create the queues, the dead-letter exchange and the fan-out exchange with its bindings. For an Amazon MQ broker, export the broker's definitions, strip what does not carry, rehearse the import on a scratch virtual host, then import for real.

  5. 5. Move producers, drain consumers

    Producers switch to the new instance first so that nothing is published to the old queues; consumers keep reading the old queues until they are empty, then switch.

  6. 6. Verify, hold the way back, retire

    Publish one message end to end, compare the definitions on both sides, record how long the source stays and what happens to messages sent in between, and delete the source only after the retention you decided.

What the run maps

From assessment.json of the run: five units from seven resources. The two SNS subscriptions fold into their topic.

AWS unitSTACKIT targetClass and confidence
aws_sqs_queue.ordersRabbitMQ queue on the shared instanceREPLATFORM, LOW
aws_sqs_queue.orders_dlqRabbitMQ queue behind a dead-letter exchangeREPLATFORM, MEDIUM
aws_sqs_queue.invoices (FIFO)RabbitMQ queue as the target, but the plan refuses to carry strict ordering and exactly-once processing: they are redesigned firstREPLATFORM, MEDIUM
aws_sns_topic.order_eventsRabbitMQ fan-out exchangeREPLATFORM, LOW
aws_mq_broker.legacy (RabbitMQ 3.13)Own stackit_rabbitmq_instance and credentialRECONFIGURE, MEDIUM

RECONFIGURE means the product is the same and the release and settings change. REPLATFORM means the programming model changes: clients leave the SQS or SNS API and speak AMQP. The queue and the topic carry LOW confidence; the finding that sets them apart is that each names another unit the draft does not create as a resource of its own.

What the draft leaves to your team

Left: two comment lines from the run's messaging.tf, copied unchanged; the other comment lines between them are left out. Right: one of the PubSub payloads the run writes as the alternative. The payloads apply only after you accept the opt-in decision for an API that may still change, and they replace the RabbitMQ instance rather than adding to it. The subscription's ackDeadline of 60s carries the queue's 60-second visibility timeout; the topic payload carries the retention as 345600s.

Draft commentsterraform/messaging.tf
# NOT CARRIED (aws_sqs_queue.orders): visibility_timeout_seconds = 60, receive_wait_time_seconds = 10 — SQS hides a received message from the other consumers for the visibility timeout and long-polls for receive_wait_time_seconds … the acknowledgement handling of the consumers is checked. (shortened: the whole reason is in the trace of this block)
# CONSUMER CHANGE (aws_sns_topic_subscription.orders_to_queue): the subscriber aws_sqs_queue.orders is a queue that a person binds to the exchange of aws_sns_topic.order_events; the subscription itself has no resource and its settings are not generated.
PubSub API payloadsterraform/pubsub-api/orders/create-subscription.json
{
  "displayName": "orders",
  "settings": {
    "ackDeadline": "60s"
  }
}
  • Dead-letter handling

    AWS moves a message to the dead-letter queue after maxReceiveCount receives, set in the redrive policy, and recommends a longer retention on the dead-letter queue than on the source queue, as this estate has (14 days against 4). In RabbitMQ, as general practice, your team declares a dead-letter exchange per queue and, on quorum queues, a delivery limit.

  • Visibility timeout

    RabbitMQ has no visibility timeout (general RabbitMQ behaviour). A message stays unacknowledged until the consumer acknowledges it or its channel closes, so consumer code that relied on the timeout is reviewed. The run names visibility_timeout_seconds and receive_wait_time_seconds as not carried for that reason.

  • FIFO and deduplication

    The plan lists the FIFO queue under what cannot be migrated: a standard queue would reorder and duplicate messages silently, so ordering and deduplication are redesigned before any target is produced, for example an ordered consumer with idempotent processing keyed on a sequence number.

The Amazon MQ broker: its own instance

The run keeps a separate instance and credential for the broker; the review comments above both blocks are left out here. Messages are not part of the definitions, and the broker users are not carried: clients get the user STACKIT generates for the credential.

Draftterraform/messaging.tf
resource "stackit_rabbitmq_instance" "legacy" {
  project_id = var.project_id
  name       = "legacy-rabbit"
  plan_name  = var.legacy_plan_name
  version    = var.legacy_version
  parameters = {
    sgw_acl = join(",", var.legacy_allowed_cidrs)
  }
}

resource "stackit_rabbitmq_credential" "legacy" {
  project_id  = var.project_id
  instance_id = stackit_rabbitmq_instance.legacy.instance_id
}
  • Version crossing

    The source runs RabbitMQ 3.13. The run flags that classic queue mirroring was removed in the 4.x line, so a mirrored classic queue becomes a quorum queue and the mirroring keys are stripped from the policies before the import.

  • Version to confirm

    The catalogue the run read lists 4.1 for the region. STACKIT's lifecycle page, as of November 2026, lists 4.0 as the current major version with no end-of-life date and gives September 2025 as the end of life of 3.13, the source's version. Confirm which version the instance accepts before the cutover.

  • Single instance noted

    The source broker is a SINGLE_INSTANCE. The plan size is a variable with a proposed default that you confirm, and the draft does not claim equal redundancy.

The broker's definitions move in four steps

From the data-move document the run writes for the broker. Each command is a template quoted from that document; the passwords come from environment variables. Owner of every step: the Data Architect.

StepWhat the run definesDone when
Preflight (read only)Export the topology: curl -u "<source-user>:$SOURCE_PASSWORD" "<source-broker-endpoint>/api/definitions" > definitions-source.json. The file holds password hashes and is kept as sensitive.The definitions, queue depths, users and clients are listed, and both management APIs answer.
RehearsalStrip users, permissions and the ha-mode policies from a copy, create a scratch virtual host (curl ... /api/vhosts/rehearsal -X PUT) and import the copy into it (curl ... /api/definitions/rehearsal -X POST).The import is accepted, refused items are settled, and the application's tests pass against the scratch host.
Initial copyImport the settled file before any producer moves: curl ... /api/definitions -X POST -d @definitions-target.json. It creates structure and moves no message.Virtual hosts, queues, exchanges, bindings and policies exist on the instance.
ReconcileExport both sides again, compare queues, exchanges and bindings, and count them with the check the document gives (curl ... /api/queues | jq length on each side). Record GO or NO-GO.The definitions match apart from recorded changes and every client has the new endpoint and credential.

Decisions your architects own

The decision register of the run holds twelve open records for this estate. These are the ones that change the design.

  • RabbitMQ or the PubSub API

    Accepting the opt-in decision for APIs that may still change switches queue-like objects to PubSub. It is the only decision here that holds plan steps.

  • Public endpoint and access list

    The RabbitMQ endpoint is public and limited by sgw_acl. The run keeps that as a default and asks you to confirm it or choose a different design if clients must not reach it from outside.

  • Plan size

    The size is proposed from the source's instance class. Confirm or choose a plan.

  • E-mail and SMS subscriptions

    The SNS topic has an e-mail subscription. AWS lists HTTP and HTTPS, e-mail, Firehose, SQS, Lambda, platform application and SMS endpoints; RabbitMQ does not deliver to e-mail, SMS, mobile push or HTTP endpoints itself. A small service of your own consumes a queue bound to the exchange and delivers.

  • Messages in flight

    Messages are not migrated by the draft. Decide whether queues drain to empty before the producers switch, or whether a replay method is rehearsed.

Questions messaging teams ask

Can our applications keep their SQS client?

No. SQS and SNS are AWS APIs. On RabbitMQ the clients speak AMQP, so producers and consumers change their library, endpoint and credentials. That is why the run classes the queues as REPLATFORM.

Does StackToIT move the messages?

No. It changes nothing in any cloud and moves no data. It writes the instance, the credential outputs and a data-move document with commands for your team to run.

What happens to the redrive policy?

It is named in the draft as not carried. Your team declares a dead-letter exchange and a limit on the queue. AWS documents that the redrive policy holds the maxReceiveCount, so that number is the one to carry over.

Do we need one instance per queue?

No. The run puts the queues and the topic on one shared instance and gives the Amazon MQ broker its own, because a broker already carries a topology that is imported as a whole.

Is this a lift and shift?

No. The broker keeps its product and changes release. Queues and topics change their programming model, which is why the plan has steps for the producers and consumers.

Sources

Your next move: STACKIT.

Tell us about your estate. We show you what StackToIT delivers for it: target architecture, Terraform artifacts and a structured migration plan.