Why
SQS is the right choice when you want a fully managed, serverless queue with no Redis dependency in production. It scales automatically, integrates natively with the rest of your AWS infrastructure, and removes the operational burden of managing a Redis cluster. Locally it runs through Localstack, so the dev loop stays self-contained inside Docker.Setup
- In
apps/backend/package.json, add"tooling-queue-sqs": "workspace:*"underdependenciesand remove any other queue adapter package. - Run
make deps-install. - In
development.ts,production.ts, andtest.ts, settools.queue: - In
compose.yml, add Localstack and optionally SQS Admin, and wirebackendandconsumerto start after Localstack is healthy. Add an init container that creates your queues before the backend starts: - In
.env.development, uncomment and set the SQS vars — placeholder values work for Localstack: - Run
make test module=tooling-queue-sqsandmake test module=backend.
Local observability
Localstack exposes its health and queue state at http://localhost:4566/_localstack/health. For a full queue UI locally, add thesqs-admin service to compose.yml (port 3999) pointed at http://localstack:4566.
Bull Board (port 3998) does not show SQS traffic — it reads Redis only. If Bull Board is still in your Compose file from a previous BullMQ setup, it will appear empty and can be removed.
Adding a new queue
- Add the queue name and its DLQ to the
QueueNameenum inapps/backend/src/utilities/queue.ts. - Add a matching config block under
tools.queue.queuesin each env config file — copy an existing entry and adjust the name and retry settings. - Add the
awslocal sqs create-queuecall for both the queue and DLQ to yoursqs-initcontainer incompose.yml— SQS does not auto-create queues. - Add the consumer to
apps/backend/src/tools/queue/consumerRegistry.ts. Skip this if you only need a producer. - Run
make test module=backend.
Gotchas
- SQS queues must be created before the consumer starts. If
sqs-inithasn’t run yet, the consumer will fail silently on startup. - Deferred enqueue (
delayMs/runAtwithjobId),replace: true, andremoveQueuedJobneedtools.cache.client(Redis) on API and consumer. SQS cannot delete in-flight messages by id — the adapter tracks generations in Redis and skips stale or cancelled messages when consuming. sendToQueuedelay is capped at 900 seconds. Longer schedules (e.g. blog publish) rely on DB state plus scheduler reconcile, same as BullMQ.- Never commit real AWS credentials in
.env.development— the placeholder values (test/test) are intentional for Localstack and safe to commit. SQS_ENDPOINTmust be omitted (or left unset) in production config. Pointing it at Localstack in production will silently swallow all messages.- Never add
@aws-sdk/client-sqsdirectly toapps/backend/package.json— it is a transitive dependency oftooling-queue-sqsand should stay that way.
What’s next?
- BullMQ — default Redis-backed provider with Bull Board.
- Configuration — switching pluggable tool implementations.
- Tooling system — how loaders and workspace packages fit together.
