In serverless architectures, a common pattern is AWS Lambda consuming messages from Amazon SQS and then calling a downstream service or external API. When the downstream service fails temporarily or returns rate limits, the system needs retries to avoid losing messages. However, retrying too quickly can make the downstream problem worse, and uncontrolled retries can push valid messages into a Dead-Letter Queue (DLQ) too early.
The AWS blog proposes a pattern that uses Amazon EventBridge Scheduler to build a custom retry mechanism for stateless queue consumers. The main idea is to move the waiting period out of Lambda. Lambda processes the message and decides when to retry, while EventBridge Scheduler sends the message back to SQS at a future time.
SQS and Lambda already provide default retry behavior, but it is not always flexible enough. Real systems often need more control:
If workflow state is already managed by Step Functions, built-in retry and backoff may be appropriate. For stateless queue consumers, this article presents a lighter pattern using EventBridge Scheduler as an external scheduler.

Source: AWS Architecture Blog - Create a serverless custom retry mechanism for stateless queue consumers
The main flow works as follows:
The important point is that the message does not stay inside Lambda while waiting. The system avoids paying for idle compute and still controls exactly when the next retry should happen.
| Service | Role in the solution |
|---|---|
| Amazon SQS | Stores the main messages and provides a DLQ for failed messages |
| AWS Lambda | Consumes messages, detects errors, and creates retry schedules |
| Amazon EventBridge Scheduler | Schedules a future delivery of the message back to SQS |
| AWS IAM | Grants Lambda permission to create schedules, pass roles, and send messages to the DLQ |
| Amazon CloudWatch | Monitors logs, metrics, retry behavior, and DLQ usage |
| AWS PrivateLink | Allows private access to EventBridge Scheduler from VPC-based Lambda functions when required |
With this pattern, each message can have its own delay. For example, HTTP 429 errors can wait 5 minutes, timeout errors can retry after 30 seconds, and validation errors can go directly to the DLQ. This is more flexible than relying only on a shared queue visibility timeout.
The article recommends using SQS message attributes or message body data to track retry attempts. Each retry updates the attempt count or timestamp. If the retry limit is exceeded, the message is no longer scheduled and is sent to the DLQ.
Partial failures are a major risk. For example, Lambda may write to a database successfully but fail when calling an external API. When the message is retried, the whole operation may run again. Consumers should therefore be idempotent, meaning the same message can be processed more than once without creating duplicate data or incorrect state.
EventBridge Scheduler has minute-level granularity, and there is additional latency between Scheduler, SQS, and Lambda. This pattern is better suited for retries measured in minutes or longer, not workflows that require exact second-level retry timing.
The system should monitor Lambda errors, duration, invocation count, schedules created, DLQ depth, and unusual retry patterns. From a security perspective, the Lambda role should have only the minimum permissions needed to create schedules, pass the scheduler role, read from SQS, and send messages to the DLQ.
Before reading this article, I mostly thought of SQS/Lambda retry behavior and Step Functions as the main options. After studying the pattern, I see EventBridge Scheduler as a useful middle ground: lighter than Step Functions, but more flexible than default retries.
I also learned that Lambda should not be used for waiting. If the system only needs to “try again later,” waiting should be handled by a scheduler, while Lambda should run only when there is real work to do.
This article presents a practical serverless pattern for retrying stateless queue consumers. By combining Amazon SQS, AWS Lambda, EventBridge Scheduler, and DLQ, the system can control retry timing more precisely, reduce pressure on downstream services, and avoid unnecessary compute cost.
For production use, I would start by classifying error types clearly, setting reasonable retry limits, designing idempotent consumers, monitoring DLQ usage closely, and validating IAM permissions so the scheduler has only the access required to send messages back to the queue.