Current section
Files
Jump to
Current section
Files
beamlab_ex_aws_sqs
README.md
README.md
# ExAws.SQS
[](https://hex.pm/packages/beamlab_ex_aws_sqs)
[](https://hexdocs.pm/beamlab_ex_aws_sqs)
[](./LICENSE)
Service module for [`ex_aws`](https://github.com/ex-aws/ex_aws).
This is a **modernized fork** of the archived
[`ex-aws/ex_aws_sqs`](https://github.com/ex-aws/ex_aws_sqs) (last released in Jan 2023). It exists
because the upstream project stopped receiving updates while its open issues stayed unresolved.
This fork:
* Switches every operation from the legacy Query/XML protocol to the **SQS JSON protocol**
([ex-aws/ex_aws_sqs#34](https://github.com/ex-aws/ex_aws_sqs/issues/34)), which AWS recommends
going forward for lower latency and less client-side overhead. This also drops the `:saxy` /
`:sweet_xml` dependency entirely — one less thing to configure or upgrade.
* Documents `send_message_batch/2` with a runnable example
([ex-aws/ex_aws_sqs#35](https://github.com/ex-aws/ex_aws_sqs/issues/35)).
* Relaxes the `:hackney` version constraint so it no longer collides with apps that have moved to
hackney 4.x ([ex-aws/ex_aws_sqs#36](https://github.com/ex-aws/ex_aws_sqs/issues/36)). `hackney`
is only used to run this library's own test suite — request execution always goes through
whatever HTTP adapter your app configures for `ex_aws`.
* Adds the message-move-task operations (`start_message_move_task/2`,
`cancel_message_move_task/1`, `list_message_move_tasks/2`) and `ListQueues`/
`ListDeadLetterSourceQueues` pagination options, none of which existed yet when upstream went
quiet.
* Refreshes `mix.exs`/CI to current Elixir/OTP versions.
## Installation
Published to Hex as `beamlab_ex_aws_sqs` because the original `ex_aws_sqs` package name is already
taken: https://hex.pm/packages/beamlab_ex_aws_sqs
```elixir
def deps do
[
{:ex_aws, "~> 2.7"},
{:beamlab_ex_aws_sqs, "~> 4.0"},
# No Jason needed — Elixir >= 1.18 has built-in JSON support.
# Use `config :ex_aws, json_codec: JSON` (or keep Jason if preferred).
{:hackney, "~> 4.0"} # or any HTTP client ex_aws supports (only for tests here)
]
end
```
Or track `main` directly:
```elixir
{:beamlab_ex_aws_sqs, github: "BeamLabEU/beamlab_ex_aws_sqs"}
```
**Note:** The public API (module `ExAws.SQS`) and configuration (`config :ex_aws, :sqs, ...`) remain the same as the original. Only the dependency name in your `mix.exs` is prefixed.
## Migrating from `ex-aws/ex_aws_sqs`
The public function names and options are unchanged — swapping the dependency source is enough to
compile. What *does* change is the shape of a successful response, because there's no more
XML-to-map parsing layer standing between you and AWS:
```elixir
# before (ex-aws/ex_aws_sqs, XML/Query protocol)
{:ok, %{body: %{messages: [%{message_id: id, body: body} | _]}}} =
ExAws.SQS.receive_message(queue_url) |> ExAws.request()
# after (this fork, JSON protocol)
{:ok, %{"Messages" => [%{"MessageId" => id, "Body" => body} | _]}} =
ExAws.SQS.receive_message(queue_url) |> ExAws.request()
```
In short: response bodies are now the raw JSON payload AWS returns, decoded by your configured
`:json_codec` (Elixir's built-in `JSON` module since 1.18, or e.g. Jason) — keyed exactly as the
[AWS API Reference](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/Welcome.html)
documents for each action, with no snake_case/atom conversion. A couple of operations use
unusual casing straight from AWS (e.g. `list_dead_letter_source_queues/1` returns a lowercase
`"queueUrls"` key) — that's an AWS quirk carried through as-is, not a bug here.
Binary message attribute values (if any) will appear base64-encoded in the raw responses.
You'll also want to drop `:saxy` and `:sweet_xml` from your deps if you added them for the old
parser, and can drop any `config :ex_aws_sqs, parser: ...` config — there's no parser to select
anymore.
One behavioral note: under the JSON protocol the `QueueUrl` travels **inside the request
body**, and the HTTP request always goes to the endpoint from your `config :ex_aws, :sqs`
(host/region) — the host embedded in the QueueUrl is *not* used to route the request (the 3.x
Query-protocol implementation derived the request path from it). This matches the official AWS
SDKs: use queue URLs whose region and account match your ex_aws config, otherwise AWS rejects
the call server-side.
## `send_message_batch/2`
Each entry is a keyword list (or map) with at least `:id` and `:message_body`:
```elixir
ExAws.SQS.send_message_batch(queue_url, [
[id: "a1", message_body: "payload1"],
[id: "a2", message_body: "payload2", delay_seconds: 10]
])
|> ExAws.request()
# {:ok, %{
# "Successful" => [%{"Id" => "a1", "MessageId" => "...", "MD5OfMessageBody" => "..."}, ...],
# "Failed" => []
# }}
```
`:id` only needs to be unique within the batch — it's how you match each entry to its result in
`"Successful"`/`"Failed"`.
Entries can be provided as keyword lists or maps.
## Message attributes
When sending messages with `:message_attributes`, use maps (or a list of maps) with `:name`,
`:data_type`, and `:value`. Supported data types are `:string`, `:number`, and `:binary`.
For binary attributes, pass the raw binary as `:value`. The library automatically base64-encodes
it to satisfy the JSON protocol on the wire.
On receive, any binary message attributes in the response will contain base64-encoded strings
under `"BinaryValue"` (this is the raw form returned by AWS under the JSON protocol).
Example:
```elixir
ExAws.SQS.send_message(queue_url, "body", message_attributes: [
%{name: "trace", data_type: :binary, value: <<1, 2, 3>>}
])
```
## Message system attributes (X-Ray tracing)
`send_message/3` and `send_message_batch/2` entries also accept
`:message_system_attributes` (same shape as `:message_attributes`). AWS currently supports
only one system attribute, `:aws_trace_header`, which carries an X-Ray trace header so
distributed tracing context survives the trip through the queue:
```elixir
ExAws.SQS.send_message(queue_url, "body",
message_system_attributes: [
%{name: :aws_trace_header, data_type: :string, value: "Root=1-67891233-abcdef012345678912345678"}
]
)
```
## Message move tasks (DLQ redrive)
These operations were added after the original library went quiet:
```elixir
# Start moving messages from a DLQ back to the source (or another) queue
{:ok, %{"TaskHandle" => handle}} =
ExAws.SQS.start_message_move_task(dlq_arn) |> ExAws.request()
# Or with options
ExAws.SQS.start_message_move_task(dlq_arn,
destination_arn: target_arn,
max_number_of_messages_per_second: 100
)
# List recent tasks
ExAws.SQS.list_message_move_tasks(dlq_arn, max_results: 5) |> ExAws.request()
# Cancel if needed
ExAws.SQS.cancel_message_move_task(handle) |> ExAws.request()
```
See the AWS docs linked from each function for details and limits.
## Streaming paginated lists
`list_queues/1` and `list_dead_letter_source_queues/2` paginate via `NextToken`. The
`stream_*` helpers do the token plumbing for you and emit items lazily:
```elixir
ExAws.SQS.stream_queues(queue_name_prefix: "prod-") |> Enum.to_list()
ExAws.SQS.stream_dead_letter_source_queues(dlq_url) |> Enum.take(50)
```
Pages are fetched with `ExAws.request/2` and the stream raises a `RuntimeError` if a page
request fails. An optional last argument is passed through as `ExAws.request/2` config
overrides (e.g. `region: "eu-west-1"`). `list_message_move_tasks/2` has no stream
counterpart — AWS returns a bounded list of recent tasks there and supports no pagination.
## Copyright and License
The MIT License (MIT)
Copyright (c) 2014 CargoSense, Inc.
Copyright (c) 2026 BeamLab EU
See [LICENSE](./LICENSE) for the full text.