Day 7/30 AWS System Design: Where Did 41,000 Messages Go?

Learn how a try/catch block in a Lambda function caused 41,000 notifications to vanish silently in a multi-tenant SaaS platform.

martes, 28 de julio de 2026 • 5 min read • Q2BSTUDIO Team

El peligro de ocultar errores con try/catch

In cloud architecture development, we often encounter incidents that seem to defy logic. One of the most instructive cases occurred in a multi-tenant SaaS platform that lost 41,000 notifications without leaving a trace. Let's analyze what happened and what lessons we can extract to design more robust systems.

The scenario is as follows: a platform sends all its background notifications (email, SMS, webhooks) through a standard SQS queue. A Lambda function consumes the messages via an event source mapping. The team correctly configured a Dead Letter Queue (DLQ) with maxReceiveCount of 5 and an alarm on DLQ depth. Everything seemed fine.

On Friday at 6 PM, a routine deployment ships two changes: first, an update to the email provider SDK that breaks the credential lookup; second, a hardening change that wraps the handler body in a try/catch that logs any exception and returns normally, with the comment 'so one bad notification can't poison the batch.' On Saturday at 9 AM, support discovers that no customer received notifications overnight: 41,000 messages lost. CloudWatch metrics show zero function errors, queue depth is zero, the DLQ is empty, the alarm never fired. All dashboards are green. Where did 41,000 messages go?

Before revealing the answer, let's evaluate the presented options. Option A suggests that messages crossed an internal receive-count threshold and SQS deleted them. No such mechanism exists: receive count only routes messages to a DLQ via a redrive policy; SQS never deletes based on receive count. Option C posits that the queue's retention period expired. Default retention is 4 days; messages a few hours old cannot expire. Moreover, expiry would not explain the zero-error, empty-DLQ picture. Option D suggests the deployment disabled the event source mapping, but an unconsumed queue shows growing depth. A queue at zero depth with no errors means messages were received, 'processed,' and deleted. Option B is correct: the handler caught every exception and returned success; Lambda reported a clean invocation, so the event source mapping called DeleteMessage on every message in the batch. The messages were deleted legitimately, one green invocation at a time.

The problem is known as 'swallowing exceptions.' By wrapping all code in a try/catch that only logs the error but does not rethrow or mark the message as failed, the handler always returns success. Lambda interprets this as correct processing and deletes the messages from the queue. The DLQ never receives anything because maxReceiveCount is never exceeded; each message is processed once (successfully from Lambda's perspective). Error logs are buried and go unnoticed until later review.

This incident highlights several design principles for cloud architectures. First, error handling must be intentional. A generic try/catch that silences failures is dangerous; exceptions must propagate so the message can be retried or sent to the DLQ. Second, monitoring should include not only function errors but also queue metrics, DLQ depth, and unexpected log patterns. Third, integration tests must cover failure scenarios, especially after changes to external dependencies.

At Q2BSTUDIO, as a software development and technology company, we understand that reliability in distributed systems is critical. Our experience with cloud AWS/Azure has taught us to implement patterns like dead-letter queues with controlled retries, circuit breakers, and observability based on metrics and alarms. We also integrate process automation to ensure deployments don't introduce silent regressions.

To avoid such disasters, we recommend the following best practices: never catch exceptions without rethrowing unless explicitly justified; use a middleware error handler at the framework level; instrument your application with custom metrics (e.g., exception counters); set alarms on DLQ depth and on the rate of messages deleted with no apparent error; and perform progressive deployments with real-time monitoring.

The case also reminds us of the importance of engineering culture: the comment 'so one bad notification can't poison the batch' reflects good intention but poor implementation. The correct solution to prevent a problematic message from affecting others is to use small batch sizes, process messages individually, or implement per-message dead-letter patterns. In Lambda, when using batches, if an invocation fails, all messages in the batch are retried; but if you catch the exception and return success, all are lost. The alternative is to return a partial failure state (e.g., marking specific messages as failed in the batch), but that requires careful implementation.

This incident also has business implications: 41,000 lost notifications mean dissatisfied customers, potential revenue loss, and reputation damage. In SaaS environments, notification delivery is a critical product component. That's why at Q2BSTUDIO we integrate BI and Power BI to monitor delivery KPIs and use AI to detect anomalies in notification patterns. Additionally, cybersecurity plays a role: a failure in email credential authentication could expose sensitive data if not handled correctly.

The final lesson is clear: in asynchronous systems, error handling is not a minor detail; it is the heart of reliability. Never assume a generic try/catch is safe. Always design with the DLQ as a safety net and monitor actively. And above all, when introducing changes, don't just rely on tests passing; verify that error paths work as expected.

At Q2BSTUDIO we help companies build custom software with robust cloud architectures. Our teams combine expertise in AWS, Azure, cybersecurity, artificial intelligence, and automation to prevent incidents like this. If you want your systems to be resilient, contact us.

In summary, the correct answer is option B. The 41,000 messages were deleted because each exception was caught and the handler returned success, causing Lambda to call DeleteMessage. The queue never showed errors because Lambda reported successful invocations. The DLQ remained empty because no message was retried. A classic case of 'swallowed exception' that every cloud architect should know.

A BREAK?

Play for a moment before you go

OUR SERVICES

How we can help you

Do you have a project in mind?

Tell us your vision and we'll turn it into a software solution. Whatever the scope, we make your idea real.