{"slug": "why-sns-silently-drops-your-messages-and-how-to-catch-it-before-you-ship", "title": "Why SNS Silently Drops Your Messages and How to Catch It Before You Ship", "summary": "A developer explains how SNS filter policies can silently drop messages when publishers omit required message attributes, causing undetected failures in event-driven AWS architectures. The post describes how this issue evades code reviews, tests, and AI coding assistants because the filter policy lives on the subscription, not in the source code. The developer introduces infrawise, a tool that extracts live infrastructure into a graph and serves it to AI assistants via MCP to make these contracts visible at coding time.", "body_md": "Your checkout service publishes an `OrderRefunded`\n\nevent to an SNS topic. The publish call returns a `MessageId`\n\n. No exception, no retry, nothing in the dead-letter queue. Three days later a customer emails asking where their refund is, and you discover the refund Lambda was never invoked.\n\nThe message wasn't lost. It was filtered. One of the topic's subscriptions has a filter policy that requires an `eventType`\n\nmessage attribute, and your publish call didn't include it. SNS did exactly what it was configured to do: it evaluated the filter, found no match, and skipped delivery for that subscription. From the publisher's side, everything looks like success.\n\nThis is one of the nastiest failure modes in event-driven AWS architectures, because every tool you'd normally reach for reports green. This post walks through why it happens, why neither your code review nor your AI coding assistant can catch it from source code alone, and how to make the contract visible at coding time instead of incident time.\n\nSNS filter policies live on the *subscription*, not the topic. A subscriber says \"only deliver messages where the `eventType`\n\nattribute is one of these values\":\n\n```\n{\n  \"eventType\": [\"order.refunded\", \"order.cancelled\"]\n}\n```\n\nFor that subscription to ever receive anything, the publisher must include the attribute:\n\n```\nawait sns.send(\n  new PublishCommand({\n    TopicArn: process.env.ORDER_EVENTS_TOPIC,\n    Message: JSON.stringify(refund),\n    MessageAttributes: {\n      eventType: { DataType: 'String', StringValue: 'order.refunded' },\n    },\n  }),\n);\n```\n\nOmit `MessageAttributes`\n\nand the publish still succeeds — SNS accepts the message and returns a `MessageId`\n\nregardless of whether any subscription matches. The message simply isn't delivered to the filtered subscription.\n\nThree things make this failure quiet:\n\n`NumberOfNotificationsFilteredOut`\n\nCloudWatch metric, but unless you already suspect filtering, nobody looks at it — and by the time you do, you're debugging in production.`PublishCommand`\n\nwas called. The mock has no filter policy. The gap between your code and the subscription's configuration is exactly the part the test can't see.There is a second variant of the same trap: filter policies can also match against the message *body* (`FilterPolicyScope: MessageBody`\n\n) instead of message attributes. Then the required keys must appear in your JSON payload itself. Same silence, different location.\n\nAsk an AI coding assistant to \"publish an order refunded event to the order-events topic\" and it will produce a perfectly reasonable `PublishCommand`\n\ncall — topic ARN from an environment variable, `JSON.stringify`\n\non the payload, maybe a comment. What it will almost never produce is the `MessageAttributes`\n\nblock with exactly the keys your subscriptions filter on.\n\nIt can't. The filter policy is not in your repository. It's a JSON document attached to a subscription in your AWS account, set by whoever wired up the consumer — possibly you, six months ago. The assistant reads your source files, sees other publish calls (which may themselves be missing attributes), and generates code that matches the pattern it found. If the pattern is wrong, the wrongness propagates.\n\nThe manual fix is miserable: open the AWS console, navigate to the topic, open each subscription one by one, read each filter policy, and paste the required attribute names into your prompt. Every session. For every topic. This is the copy-paste loop that breaks flow — and if you skip it once, you ship the silent drop.\n\nThis is the problem [infrawise](https://github.com/Sidd27/infrawise) is built for: it extracts your live infrastructure into a graph and serves it to your AI assistant over MCP, so the assistant queries facts instead of guessing.\n\n```\ncd your-project\nnpx infrawise start --claude\n```\n\nThat analyzes your AWS account and codebase, writes `.mcp.json`\n\n, and opens Claude Code with 21 MCP tools connected. For SNS specifically, the extraction is read-only and direct: infrawise lists your topics, then for every confirmed subscription fetches its attributes, parses the `FilterPolicy`\n\nJSON, and records which attribute keys the policy requires and whether the policy scope is `MessageAttributes`\n\nor `MessageBody`\n\n.\n\nThe result is exposed through a tool called `get_topic_details`\n\n. For each topic it returns the subscription count, encryption status, and a `filterPolicies`\n\narray:\n\n```\n{\n  \"name\": \"order-events\",\n  \"provider\": \"aws\",\n  \"subscriptionCount\": 3,\n  \"encrypted\": true,\n  \"filterPolicies\": [\n    {\n      \"subscriptionArn\": \"arn:aws:sns:...:order-events:a1b2...\",\n      \"protocol\": \"sqs\",\n      \"requiredAttributes\": [\"eventType\"],\n      \"scope\": \"MessageAttributes\"\n    }\n  ]\n}\n```\n\nNow the workflow changes. When you ask your assistant to write a publish call, it calls `get_topic_details`\n\nfirst, sees that a subscription on `order-events`\n\nfilters on `eventType`\n\n, and writes the `MessageAttributes`\n\nblock into the code on the first attempt. The contract that used to live invisibly in a subscription's configuration is now part of the context every generated publish call is checked against.\n\nThe tool description itself tells the assistant when to use it — before writing any SNS publish code — so you don't have to remember to prompt for it. And because infrawise also scans your application code (an AST pass that recognizes `PublishCommand`\n\n, `PublishBatchCommand`\n\n, and SDK `publish`\n\ncalls and resolves their target topic ARNs), the graph knows which of your functions publish to which topics. Reviewing an existing publisher works the same way: the assistant can cross-reference what the function sends against what the topic's subscriptions require.\n\nA few boundaries worth stating, because tools that read your AWS account should be explicit about them. Infrawise is read-only — the SNS extraction uses `GetTopicAttributes`\n\n, `ListSubscriptionsByTopic`\n\n, and `GetSubscriptionAttributes`\n\n, nothing that writes. It never reads message contents, secret values, or parameter values. And the analysis is deterministic: AST parsing and API introspection, no LLM deciding what your infrastructure looks like. The LLM is only a consumer of the extracted context.\n\nSilent message drops are a configuration-versus-code mismatch, and those never show up in the layer you're staring at. The filter policy is correct. The publish code is correct. Only the combination is broken, and the combination exists nowhere in your repository — until you put it there.\n\nYou can do that manually every session by reading subscriptions in the console, or you can have it extracted once and served to your assistant automatically. If you're on the second option: [GitHub](https://github.com/Sidd27/infrawise) · [npm](https://www.npmjs.com/package/infrawise) — `npx infrawise start --claude`\n\nand ask your assistant what the `order-events`\n\ntopic requires.\n\n`MessageId`\n\nreturned, no DLQ entry, no error. The redrive policy only covers delivery failures, and a filter mismatch isn't one.`FilterPolicyScope`\n\n: policies can require message `get_topic_details`\n\ngives your AI assistant that list automatically.`NumberOfNotificationsFilteredOut`\n\nCloudWatch metric is your post-hoc signal, but the goal is to never need it — catch the missing attribute at coding time.", "url": "https://wpnews.pro/news/why-sns-silently-drops-your-messages-and-how-to-catch-it-before-you-ship", "canonical_source": "https://dev.to/siddharth_pandey_27/why-sns-silently-drops-your-messages-and-how-to-catch-it-before-you-ship-1fcm", "published_at": "2026-07-11 08:53:46+00:00", "updated_at": "2026-07-11 09:14:08.695772+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools"], "entities": ["AWS", "SNS", "infrawise", "Claude Code", "MCP"], "alternates": {"html": "https://wpnews.pro/news/why-sns-silently-drops-your-messages-and-how-to-catch-it-before-you-ship", "markdown": "https://wpnews.pro/news/why-sns-silently-drops-your-messages-and-how-to-catch-it-before-you-ship.md", "text": "https://wpnews.pro/news/why-sns-silently-drops-your-messages-and-how-to-catch-it-before-you-ship.txt", "jsonld": "https://wpnews.pro/news/why-sns-silently-drops-your-messages-and-how-to-catch-it-before-you-ship.jsonld"}}