cd /news/developer-tools/why-sns-silently-drops-your-messages… · home topics developer-tools article
[ARTICLE · art-55335] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

Why SNS Silently Drops Your Messages and How to Catch It Before You Ship

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.

read5 min views1 publishedJul 11, 2026

Your checkout service publishes an OrderRefunded

event to an SNS topic. The publish call returns a MessageId

. 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.

The message wasn't lost. It was filtered. One of the topic's subscriptions has a filter policy that requires an eventType

message 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.

This 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.

SNS filter policies live on the subscription, not the topic. A subscriber says "only deliver messages where the eventType

attribute is one of these values":

{
  "eventType": ["order.refunded", "order.cancelled"]
}

For that subscription to ever receive anything, the publisher must include the attribute:

await sns.send(
  new PublishCommand({
    TopicArn: process.env.ORDER_EVENTS_TOPIC,
    Message: JSON.stringify(refund),
    MessageAttributes: {
      eventType: { DataType: 'String', StringValue: 'order.refunded' },
    },
  }),
);

Omit MessageAttributes

and the publish still succeeds — SNS accepts the message and returns a MessageId

regardless of whether any subscription matches. The message simply isn't delivered to the filtered subscription.

Three things make this failure quiet:

NumberOfNotificationsFilteredOut

CloudWatch metric, but unless you already suspect filtering, nobody looks at it — and by the time you do, you're debugging in production.PublishCommand

was 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

) instead of message attributes. Then the required keys must appear in your JSON payload itself. Same silence, different location.

Ask an AI coding assistant to "publish an order refunded event to the order-events topic" and it will produce a perfectly reasonable PublishCommand

call — topic ARN from an environment variable, JSON.stringify

on the payload, maybe a comment. What it will almost never produce is the MessageAttributes

block with exactly the keys your subscriptions filter on.

It 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.

The 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.

This is the problem 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.

cd your-project
npx infrawise start --claude

That analyzes your AWS account and codebase, writes .mcp.json

, 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

JSON, and records which attribute keys the policy requires and whether the policy scope is MessageAttributes

or MessageBody

.

The result is exposed through a tool called get_topic_details

. For each topic it returns the subscription count, encryption status, and a filterPolicies

array:

{
  "name": "order-events",
  "provider": "aws",
  "subscriptionCount": 3,
  "encrypted": true,
  "filterPolicies": [
    {
      "subscriptionArn": "arn:aws:sns:...:order-events:a1b2...",
      "protocol": "sqs",
      "requiredAttributes": ["eventType"],
      "scope": "MessageAttributes"
    }
  ]
}

Now the workflow changes. When you ask your assistant to write a publish call, it calls get_topic_details

first, sees that a subscription on order-events

filters on eventType

, and writes the MessageAttributes

block 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.

The 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

, PublishBatchCommand

, and SDK publish

calls 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.

A 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

, ListSubscriptionsByTopic

, and GetSubscriptionAttributes

, 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.

Silent 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.

You 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 · npmnpx infrawise start --claude

and ask your assistant what the order-events

topic requires.

MessageId

returned, no DLQ entry, no error. The redrive policy only covers delivery failures, and a filter mismatch isn't one.FilterPolicyScope

: policies can require message get_topic_details

gives your AI assistant that list automatically.NumberOfNotificationsFilteredOut

CloudWatch metric is your post-hoc signal, but the goal is to never need it — catch the missing attribute at coding time.

── more in #developer-tools 4 stories · sorted by recency
── more on @aws 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/why-sns-silently-dro…] indexed:0 read:5min 2026-07-11 ·