COSS

Suppressions

Addresses that sends skip, after a bounce, a complaint or an unsubscribe.

Each environment has its own suppression list. When you send, every recipient (to, cc and bcc) with a matching entry is skipped: it gets the status suppressed and an email.suppressed event, and is never handed to the mail provider. If no recipient is left, the email fails with all_recipients_suppressed.

Scopes

An entry's scope is * (every email) or a category. A send with category: "marketing" skips addresses suppressed for * or marketing; a send without a category only skips * entries.

Automatic entries

A permanent bounce or a complaint adds a * entry for that address, with reason bounce or complaint and the email_id that caused it. Mailbox simulator addresses (@simulator.amazonses.com) are never suppressed, so they keep working for tests.

Managing the list

Manage entries in the dashboard (Suppressions) or through the API. Adding an entry for an address and scope that already has one returns the existing entry.

// A recipient unsubscribed from marketing email.
await ...({ : "ada@example.com", : "marketing" });

// Would a marketing email reach them?
const { :  } = await ...({
  : "ada@example.com",
  : "marketing",
});

// They resubscribed.
if (?.) {
  for (const  of .) {
    await ...(.);
  }
}

Removing an entry lets later sends reach the address again. Remove automatic entries with care: repeatedly sending to addresses that bounce or complain hurts your sending reputation.

Importing and exporting

Move whole lists in and out, for example when you switch from another provider. import takes any number of entries and sends them 1,000 per request. Each entry is validated on its own: invalid ones are skipped and reported by their position, and entries already on the list are left unchanged, so importing the same list again is safe. Imported entries have the reason import.

const { :  } = await ...({
  : [
    { : "ada@example.com" },
    { : "grace@example.com", : "marketing" },
  ],
});
// result?.created, result?.existing, result?.invalid

// The whole list as CSV: address,scope,reason,created_at,email_id,id (here only bounces).
const { :  } = await ...({ : "bounce" });

The dashboard's Suppressions page imports a CSV file of up to 10,000 rows and downloads the same export. A file needs an address (or email) column and may have a scope (or category) column; a file without a header row has the address first and an optional scope second. An export imports again as is. Cells a spreadsheet would run as a formula (starting with =, +, - or @) are exported with a leading ', which the import removes.

On this page