Bounces, complaints and suppression in Amazon SES, explained
Amazon SES suppresses hard bounces and complaints automatically. It doesn't manage your list, stop reimports, or know about unsubscribes.
Amazon SES will pause your account if your bounce rate or complaint rate climbs too high, and it runs an account-level suppression list to help keep those rates down. That list does real work: once an address hard-bounces or complains, SES stops delivering to it, and the failed attempt no longer counts against your reputation.
What it doesn’t do is manage your list. It doesn’t stop you re-importing an address you already bounced; the send just fails silently again. It doesn’t clean out repeat soft bouncers. And it has no concept of an unsubscribe at all. Everything past hard bounces and complaints is work you build.
Key Takeaways
- SES’s account-level suppression list auto-adds hard bounces and complaints, and blocks delivery to them without hurting your bounce or complaint rate on repeat sends.
- It never adds soft bounces, and it has no idea what an unsubscribe is. Both are entirely your own build.
- Amazon reviews your account at 5% bounces or 0.1% complaints, and can pause sending at 10% bounces or 0.5% complaints. Amazon’s own recommended targets are tighter: under 2% and under 0.1%.
- The suppression list is scoped to one AWS account in one region. Send from two regions and you’re keeping two lists.
- A suppressed re-send still costs you a slot in your daily sending quota, even though SES quietly drops it.
What SES calls a bounce
Every bounce SES reports carries a bounceType of Permanent, Transient, or Undetermined, defined in Amazon’s own notification schema. Permanent is what most people mean by hard bounce: the mailbox or domain doesn’t exist, and sending there again won’t help. Transient is a soft bounce: a full mailbox, an oversized message, content the receiving server rejected. Undetermined means the receiving provider didn’t say enough for SES to tell which one it was.
Only permanent bounces count toward your bounce rate. Transient ones don’t, no matter how many pile up for the same address.
| SES calls it | Common subtypes | What it means for the address |
|---|---|---|
| Permanent (hard) | General, NoEmail | Gone for good. Remove it. |
| Transient (soft) | MailboxFull, MessageTooLarge, ContentRejected, AttachmentRejected | Temporary. Might deliver next time. |
| Undetermined | Undetermined | Not enough information to classify either way. |
What SES calls a complaint
A complaint isn’t SES guessing. It’s a mailbox provider telling SES, through a feedback loop, that a recipient pressed report spam. SES’s complaint schema carries a complaintFeedbackType of abuse, fraud, not-spam, virus, auth-failure, or other, a shared IANA vocabulary every major provider uses.
Two things about complaints don’t work like bounces. Most providers strip the actual complaining address before forwarding the report, so SES’s complainedRecipients list is its best reconstruction from the original send, not a certainty. And Gmail doesn’t participate in SES’s feedback loop at all: someone hitting Report Spam in Gmail never reaches SES as a complaint event, so your complaint rate structurally undercounts your worst provider.
How the events reach you
By default, SES emails bounce and complaint notices to the address on your AWS account, as plain text. That works for a handful of sends a month and stops working the moment it isn’t.
The alternative is event publishing: attach a configuration set to your sends, and every event, Bounce, Complaint, Delivery and more, is published as JSON to a destination you choose. Amazon SNS is the common one, and from there most systems drop the events on an SQS queue, poll it, and write each event into their own database. A connected AWS account’s own role is usually scoped to create exactly that SNS topic and nothing else, the kind of narrow grant covered in connecting an AWS account safely. CloudWatch and Kinesis Firehose are the other two, useful when you want the numbers in a dashboard rather than a row you can query per address. This is the same shape any production sender ends up building: bounces and complaints don’t stay useful as an email in an inbox, only once code has turned them into a decision.
The JSON itself is small and structured. A bounce notification carries a bounceType, a bounceSubType, and a bouncedRecipients list with the failing address; a complaint carries complainedRecipients and the feedback type above. Neither one tells you what to do next. That’s the suppression list’s job, and it’s also where the automation stops.
What the account-level suppression list does for you
SES has run an account-level suppression list by default for every account created after November 25, 2019 (earlier accounts have to turn it on). Configured for both reasons, it does three things without you writing any code:
- Adds an address the moment it produces a hard bounce, or a complaint.
- Refuses delivery to a suppressed address on any later send: SES accepts the API call, then silently drops it.
- Keeps that repeat failure from counting against your bounce or complaint rate. It’s logged separately, under the
BounceorComplaintmetric rather thanReputation.BounceRateorReputation.ComplaintRate.
Two limits are worth knowing before you rely on it. It only ever adds hard bounces, never soft ones, no matter how many times the same address bounces temporarily. And it’s scoped to one AWS account in one region: sending from us-east-1 and eu-west-1 means keeping two separate lists, and a hard bounce in one never protects the other.
Entries stay until you remove them, with one exception: if your account’s sending gets paused, SES purges the whole list after 90 days if the pause hasn’t been lifted by then. A suppressed re-send also still counts against your daily sending quota, even though nothing gets delivered, so a stale list is wasted headroom as well as wasted trust.
There’s a second, older layer underneath this one. SES also runs a global suppression list shared across every SES customer: any address that hard-bounces anywhere on SES gets blocked for everyone, for up to 14 days, longer on repeat offenses. You can’t query it or manage it directly. It protects SES as a whole rather than your own list specifically, and the 14-day expiry is exactly why it isn’t a substitute for your own account-level list.
The thresholds that put your account under review
Amazon publishes the exact lines. Cross them and here’s what happens:
| Metric | Amazon’s recommended target | Puts your account under review | Can pause your sending |
|---|---|---|---|
| Bounce rate | Below 2% | 5% or higher | 10% or higher |
| Complaint rate | Below 0.1% | 0.1% or higher | 0.5% or higher |
Neither rate is measured over a fixed window like 24 hours or 30 days. Amazon looks at a representative volume sized to your own sending pattern, so you can’t reproduce the exact number from the console or the GetSendStatistics API; you can only stay well clear of it. Under review, you can keep sending while you fix the cause. Paused means every send is refused until Amazon lifts it, whether it’s a raw API call or a tool sitting in front of one, and only Amazon can lift it: see what to do if AWS pauses your account for the practical steps.
Bounce rate only counts permanent bounces, so a spike in transient ones, a bad day for a big mailbox provider, won’t put you under review by itself. Complaint rate is worse than it looks for the reason above: it’s calculated only from providers that run a feedback loop with SES, which excludes Gmail. If Gmail users are unhappy with your mail, that unhappiness doesn’t show up in the number Amazon is watching, right up until it shows up somewhere else.
What’s still entirely on you
Everything above is real protection, and it only covers two of the ways a list goes bad.
Soft bounces. SES never suppresses on its own no matter how many times the same address bounces temporarily. A policy like “suppress after three soft bounces in 30 days” is something you build and store yourself. See soft bounces vs hard bounces for why the two need different rules.
Unsubscribes. SES has exactly two automatic suppression reasons: bounce and complaint. It has no concept of an unsubscribe. The one-click header required by Gmail and Yahoo’s bulk sender rules tells a mail client to POST somewhere, but what that endpoint does, whether it actually stops future sends, is entirely your application’s responsibility. SES will happily keep delivering to someone who unsubscribed yesterday if nothing on your side recorded it. See customising the unsubscribe experience for what that recording actually has to do.
Re-imports. The account-level list stops a suppressed address from being delivered to again. It does nothing to stop your own system from believing that address is active. Re-import a CSV that includes a hard-bounced address, and your database shows it as a normal contact again; the next send looks successful in your own logs while SES quietly drops it on the wire. The suppression list protects your reputation from that mistake. It does not tell you the mistake happened. See suppressions: why an address can’t be re-added.
List hygiene generally. Pruning contacts who never open or click, holding a proper double opt-in, watching a new list’s first few sends closely: none of it is an SES feature. It’s the same job under either a hosted platform or a hand-rolled script, on SES or any other provider.
The gap here is exactly what AWS’s own production access form checks for. When you request production access, the form asks how you handle bounces and complaints and how people unsubscribe, because the account-level list only covers the first two, and Amazon knows it. What a strong answer to that form actually looks like is covered in getting out of the SES sandbox.
Building this, or buying it
None of this changes based on who’s sending. A script hitting the SES API directly needs its own durable suppression table, its own soft-bounce counter, and its own unsubscribe handling, because SES’s list stops at hard bounces and complaints. A hosted tool sitting in front of SES does the same work: read the events, apply SES’s own suppression, then layer soft-bounce policy, unsubscribes and list hygiene on top.
This is one piece of what SES does and doesn’t give you; the complete guide to Amazon SES for email marketing covers the rest.
If you’re pricing this out yourself, what SES actually costs once bounces, complaints and everything else are added up is worth reading before you assume the sending API is the whole bill. And if you’d rather not build the suppression, unsubscribe and hygiene layer from scratch, BigMailer and EmailFig are two of the tools that already sit on top of SES doing exactly that. EmailFig’s BYOS plans connect to your own AWS account, so the rates and thresholds above are yours directly, and it’s free up to 4,000 subscribers either way.