Updated Nov 5, 20266 min read

How to Use AWS SQS with Node.js in 2026: Send, Receive and Dead-Letter Queues

Send and consume Amazon SQS messages from Node.js with the AWS SDK v3, get long polling, visibility timeouts and dead-letter queues right, and see how to get the same queue setup from TypeScript declarations, with no polling loop to maintain.

Amazon SQS is the default queue on AWS for moving work out of a Node.js request: sending emails, processing uploads, calling slow third-party APIs. The SDK calls are short, and the work is in the details around them, such as long polling, the visibility timeout, deleting messages only after success, dead-letter queues and the IAM permissions for every producer and consumer.

This guide covers the AWS SDK v3 for JavaScript end to end, with a producer, a consumer loop and a dead-letter queue, and then shows how Encore (a tool that works alongside your framework) provisions the same SNS and SQS setup in your AWS account from TypeScript declarations.

At a glance

ConceptWhat to know
Package@aws-sdk/client-sqs (SDK v3, modular)
Max message size1 MiB
Long pollingWaitTimeSeconds: 20 on receive
Batch sizeUp to 10 messages per send, receive or delete
Retry mechanismVisibility timeout; undeleted messages reappear
Poison messagesDead-letter queue with maxReceiveCount
OrderingStandard queues are best-effort; FIFO queues order per message group

Send a message

Install the SQS client from the AWS SDK for JavaScript v3, which ships each service as its own package:

~/app
$ npm install @aws-sdk/client-sqs
// producer.ts import { SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs"; const sqs = new SQSClient({ region: "eu-central-1" }); const QueueUrl = process.env.ORDERS_QUEUE_URL!; export async function enqueueOrder(orderId: string) { await sqs.send( new SendMessageCommand({ QueueUrl, MessageBody: JSON.stringify({ orderId }), }), ); }

The client resolves credentials through the default provider chain: environment variables locally, and the IAM role attached to the Lambda function, ECS task or EC2 instance in AWS. Messages can be up to 1 MiB, and SendMessageBatchCommand sends up to 10 at a time.

Receive and delete messages

A consumer is a loop that long-polls the queue, processes each message and deletes it once the work succeeded:

// consumer.ts import { SQSClient, ReceiveMessageCommand, DeleteMessageCommand, } from "@aws-sdk/client-sqs"; const sqs = new SQSClient({ region: "eu-central-1" }); const QueueUrl = process.env.ORDERS_QUEUE_URL!; async function handle(body: { orderId: string }) { console.log("processing order", body.orderId); } export async function poll() { while (true) { const { Messages = [] } = await sqs.send( new ReceiveMessageCommand({ QueueUrl, MaxNumberOfMessages: 10, WaitTimeSeconds: 20, }), ); for (const m of Messages) { try { await handle(JSON.parse(m.Body!)); await sqs.send( new DeleteMessageCommand({ QueueUrl, ReceiptHandle: m.ReceiptHandle! }), ); } catch (err) { // Not deleted: the message becomes visible again after the timeout. console.error("failed", m.MessageId, err); } } } }

WaitTimeSeconds: 20 turns on long polling, so the call waits for messages instead of returning empty immediately, which cuts empty responses and request count. A received message is hidden from other consumers for the queue's visibility timeout, 30 seconds by default, and if the handler takes longer the message reappears and a second consumer processes it in parallel. Set the timeout above your slowest expected handler, or extend it for long jobs with ChangeMessageVisibilityCommand.

Standard queues deliver at least once and occasionally deliver a message twice, so handlers should be idempotent, for example by recording processed order IDs in the database and skipping repeats.

Dead-letter queues and FIFO

A message that throws on every attempt keeps coming back until its retention period ends, four days by default. A dead-letter queue catches it after a set number of receives: create a second queue and set a redrive policy on the source queue with deadLetterTargetArn and maxReceiveCount, for example 5. Alarm on the dead-letter queue depth in CloudWatch, and use the SQS redrive feature to move messages back after a fix.

FIFO queues, with names ending in .fifo, preserve order within a MessageGroupId and deduplicate within a five-minute window using MessageDeduplicationId or content-based deduplication. Order handling has a throughput cost, so use FIFO only where order matters, such as events for a single account.

What the SDK approach leaves to you

Beyond the SDK calls, each queue and dead-letter queue needs to exist in every environment, usually through Terraform or CDK, along with an IAM policy for each producer and consumer, the queue URL in each service's configuration, a process that runs the polling loop and restarts it, and a local substitute such as LocalStack for development. When one event needs to reach several consumers, an SNS topic fans out to one queue per consumer, with subscriptions and queue policies on top.

Set up the Encore app

Get started
brew install encoredev/tap/encore &&
encore app create
Copy

Run in your terminal to get started locally.

~
$ encore app create shop --example=ts/empty$ cd shop && mkdir orders

SQS-backed Pub/Sub from TypeScript declarations with Encore

Encore

Encore is a tool that works alongside your framework for TypeScript and Go, with a platform that provisions and deploys into your own AWS or GCP account. A Pub/Sub topic and its subscriptions are declared in TypeScript, and on AWS Encore provisions them as SNS and SQS with dead-letter queues and runs each subscription's handler for you, with no polling loop or queue URLs in your code.

The Infrastructure page in the Encore dashboard with a database's configuration panel open, showing fields for vCPUs, memory, storage, availability type, max connections and backups.
Each provisioned resource can be reviewed and its configuration edited per environment in the Encore dashboard. The screenshot shows a GCP Cloud SQL database.
// orders/encore.service.ts import { Service } from "encore.dev/service"; export default new Service("orders");
// orders/orders.ts import { api } from "encore.dev/api"; import { Topic, Subscription } from "encore.dev/pubsub"; interface OrderPlaced { orderId: string; } export const orderPlaced = new Topic<OrderPlaced>("order-placed", { deliveryGuarantee: "at-least-once", }); export const placeOrder = api( { expose: true, method: "POST", path: "/orders" }, async ({ orderId }: { orderId: string }): Promise<{ ok: boolean }> => { await orderPlaced.publish({ orderId }); return { ok: true }; }, ); // Throwing an error makes Encore retry the message. const _ = new Subscription(orderPlaced, "send-confirmation", { handler: async (event) => { console.log("sending confirmation for", event.orderId); }, });

encore run runs the topic with an in-memory implementation, so publishing from the endpoint and receiving in the subscription works locally without AWS credentials or LocalStack, and the local development dashboard shows the publish and the handler call in its traces. A second consumer is another new Subscription on the same topic, and Encore manages the subscription setup on SNS and SQS for it. Handlers should still be idempotent, since delivery is at least once on SQS either way.

On deploy, Encore provisions the SNS topic, the SQS queues with dead-letter queues, and an IAM role for the service, as described in the AWS infrastructure docs. The same code deploys to GCP Pub/Sub with dead-letter topics if you choose GCP. Connecting your AWS account is covered in deploying to your own cloud. A coding agent adding a consumer writes a handler in the service that needs it.

Best fit: TypeScript teams on AWS who want SQS-backed queues with dead-letter handling and local development from TypeScript declarations.

Frequently asked questions

How do I send a message to SQS from Node.js?

Install @aws-sdk/client-sqs, create an SQSClient for your region, and send a SendMessageCommand with the QueueUrl and a MessageBody string, usually JSON. The SDK picks up credentials from the environment, a shared profile or the IAM role of the instance or container.

How do I consume SQS messages in Node.js?

Run a loop that sends ReceiveMessageCommand with WaitTimeSeconds set to 20 for long polling and MaxNumberOfMessages up to 10, processes each message, and deletes it with DeleteMessageCommand using its ReceiptHandle. A message that is not deleted becomes visible again after the visibility timeout and is retried.

What is a dead-letter queue in SQS?

A second queue that receives messages which failed processing too many times. You attach it to the source queue with a redrive policy and a maxReceiveCount, so a message that keeps failing moves aside instead of being retried forever and blocking capacity.

How do I get SQS-backed queues from TypeScript without the AWS SDK?

With Encore, declare a topic with new Topic and a consumer with new Subscription and a handler, both from encore.dev/pubsub. Locally, encore run uses an in-memory implementation, and on AWS Encore provisions SNS and SQS with dead-letter queues and runs the handler for each message.

Deploy to your cloud on AWS and Google Cloud
without managing infrastructure

Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.

~/orders — claude
Claude Code
Claude Code v2.1.180Opus 5.5~/orders