A stateless transport bridge – routes messages between two AWS SQS queues without sessions.
You have an ingestion queue receiving events from upstream producers and need to route those messages to a processing queue consumed by a downstream microservice. Both queues are standard SQS queues in the same AWS region.
flowchart LR
Q1["SQS Queue\ningestion-events"]
Q2["SQS Queue\nprocessing-events"]
Q1 -->|ReceiveMessage\nlong-poll| R[Receiver\nsqs-in]
R --> Route[Route\nforward]
Route --> S[Sender\nsqs-out]
S -->|SendMessageBatch| Q2
style Route fill:#f96,stroke:#333
bridge:
id: sqs-forwarder
receivers:
- id: sqs-in
transport: sqs
options:
queue_url: https://sqs.us-west-1.amazonaws.com/123456789/ingestion-events
region: us-west-1
max_messages: 10
wait_time_seconds: 20
visibility_timeout: 60
auto_extend: true
senders:
- id: sqs-out
transport: sqs
options:
queue_url: https://sqs.us-west-1.amazonaws.com/123456789/processing-events
region: us-west-1
batch_size: 10
bindings:
- id: to-processing
sender_id: sqs-out
address: processing-events
stores:
# The default policy dead-letters permanent failures and expired messages;
# a route that says so needs a store to write them to.
dlq:
type: sqlite
options:
path: /var/lib/gobridge/dlq.db
routes:
- id: forward
receiver_id: sqs-in
delivery_mode: direct_hold
dispatch_mode: single
bindings: [to-processing]
SQS is a stateless transport. Unlike MQTT, there’s no persistent connection to manage. Each receive and send operation is an independent HTTP API call. This means:
sessions section neededtransport: sqs directlysession_id referencesreceiversqueue_url – Fully qualified SQS queue URL. Alternatively use queue_name for automatic URL resolution.region: us-west-1 – AWS region. If omitted, uses the SDK default chain (env vars, instance profile, etc.).max_messages: 10 – Maximum messages per ReceiveMessage call (SQS max is 10).wait_time_seconds: 20 – Long polling. The API call waits up to 20 seconds for messages before returning empty. This reduces cost and latency vs short polling.visibility_timeout: 60 – After receiving a message, it becomes invisible to other consumers for 60 seconds. If not acknowledged in time, SQS re-delivers it.auto_extend: true – A background goroutine renews the visibility timeout at one-third of the window (visibility_timeout/3, floored at 1s; the 20s mark for this 60s timeout), preventing redelivery for long-running processing.sendersbatch_size: 10 – Maximum entries per SendMessageBatch call. It takes effect only when a caller invokes the sender’s SendBatch (batch) API directly. This route’s per-delivery dispatch sends one message per Send call, so batch_size does not reduce API calls here, and it does not apply to the shared-outbox drain path either (the drainer sends one record per Send).Note on the SQS binding
address. An SQS sender is pinned to one queue via itsqueue_urlorqueue_name. The bindingaddressmay be the bare queue name (as here,processing-events) or the full queue URL – either form is matched to that bound queue. It names the sender’s queue rather than routing per message.
sequenceDiagram
participant Bridge as GoBridge Receiver
participant SQS as SQS Queue
loop Every poll cycle
Bridge->>SQS: ReceiveMessage(max=10, wait=20s)
SQS-->>Bridge: 0-10 messages
alt Messages received
Bridge->>Bridge: Process via route
Bridge->>SQS: DeleteMessage (ACK)
end
opt auto_extend at one-third of visibility
Bridge->>SQS: ChangeMessageVisibility
end
end
reg := ports.NewRegistry()
_ = sqs.Register(reg) // register the linked adapter's config decoder
cfg, _ := cfgparser.ParseFile("bridge.yaml", cfgparser.FormatAuto, reg)
rt, _ := bridge.NewBuilder(cfg, bridge.WithLogger(logger)).
RegisterTransportFactory("sqs", sqs.NewFactory(logger)).
Build(ctx)
rt.Start(ctx)
If you prefer logical names over URLs, the SQS adapter resolves them at startup:
receivers:
- id: sqs-in
transport: sqs
options:
queue_name: ingestion-events
region: us-west-1
For local development with LocalStack:
receivers:
- id: sqs-in
transport: sqs
options:
queue_url: http://localhost:4566/000000000000/ingestion-events
endpoint: http://localhost:4566
region: us-west-1
For ordered, exactly-once processing:
senders:
- id: sqs-out
transport: sqs
options:
queue_url: https://sqs.us-west-1.amazonaws.com/123456789/events.fifo
fifo: true
message_group_id: default-group
batch_size: 10
The message_group_id determines ordering scope. Messages in the same group are delivered in order. Use fifo: true to enable FIFO semantics even when the group ID comes from envelope headers.
When SQS receives messages via an SNS subscription, they arrive wrapped in an SNS envelope. Enable unwrapping to extract the original message:
receivers:
- id: sqs-in
transport: sqs
options:
queue_url: https://sqs.us-west-1.amazonaws.com/123456789/events
sns_unwrap: true
Use a specific AWS shared-config profile:
receivers:
- id: sqs-in
transport: sqs
options:
queue_name: ingestion-events
region: us-west-1
profile: production