Skip to content

554 Recipient count exceeds 50 (Amazon SES)

The error, as SES returns it at the end of DATA:

554 Transaction failed: Recipient count exceeds 50

If you relay through Mailway, this is already handled

Section titled “If you relay through Mailway, this is already handled”

Above a provider’s recipient cap, Mailway rewrites the message so SES accepts it and delivers to everyone. You don’t need to split the send yourself. Skip to what changes for your recipients, because one thing does.

If you send through the HTTP API instead, you never see this error: the API rejects more than 50 recipients up front with a 422, before anything is queued.

This is the part that is easy to get wrong. SES does not count the SMTP envelope. It counts the union of the envelope and the To/Cc/Bcc header addresses, and it evaluates that count after the DATA terminator — so every RCPT TO can be accepted with 250 Ok and the message can still be rejected wholesale a moment later.

Probed against live SES in eu-central-1, 2026 Q3:

Envelope (RCPT TO) To: header Result
1 51 addresses 554 exceeds 50
2 undisclosed-recipients:; (0) 250 Ok, delivered
51 undisclosed-recipients:; (0) 554 exceeds 50

Row 1 proves the header alone trips the cap — the envelope held one address. Row 3 proves the envelope still counts too — the header was empty. Both are counted, together.

The consequence is the trap: splitting the envelope alone fixes nothing. Send 52 recipients as two chunks of 26 RCPT TO while DATA still carries all 52 addresses in To:, and both chunks fail exactly as the unsplit message did.

This also contradicts the intuition most people bring from AWS’s own “send to multiple recipients” material, which describes the v2 API and its explicit Destinations argument, where header and envelope are decoupled. On the SMTP and SendRawEmail path SES re-derives the count from the message itself. AWS’s troubleshooting documentation is the one that matches observed behavior: a recipient is any address on the To:, CC:, or BCC: lines.

Above the cap, and only above it, Mailway:

  1. Removes every To/Cc/Bcc header from the raw message, including folded continuation lines, and appends one empty group: To: undisclosed-recipients:;. Header count becomes 0.
  2. Chunks the SMTP envelope into groups of at most the provider’s cap and dials once per chunk.

Union per chunk is then 0 + (≤ cap), which SES accepts. This is classic BCC fan-out.

Two details worth knowing:

  • The fan-out leaves the body alone. The rewrite operates on the header block only, so the body, and therefore the DKIM body hash, stays as it is. A sender signature that covered To/Cc/Bcc is invalidated, which is acceptable because SES re-signs the modified headers with your domain’s own DKIM and DMARC aligns on that signature.
  • It is one of four automatic changes Mailway makes to a relayed message. The second is the provider instruction headers: a few headers Mailway adds at the top of the message for the provider to read and remove (for SES, X-SES-CONFIGURATION-SET), so your recipient never sees them. The third: a bare line feed or a bare carriage return reaches the provider as a carriage return and a line feed (CRLF). The fourth: a message without a Message-ID or Date gets one when Mailway receives it, and a Bcc field is removed. A fifth change, re-encoding for a structured-only API provider, is opt-in: it is off by default and an admin must turn it on. Mailway otherwise relays byte-for-byte. The fan-out exists because SES cannot accept the message as sent, and it fires only when the recipient count exceeds the cap. At or below the cap, the recipient headers stay as they are and the envelope is never even computed.

The cap is a per-provider property, not an SES special case. Both SES transports declare 50; providers that impose no such limit take the ordinary single-dial path.

Because the send becomes a BCC:

  • Recipients no longer see each other. Each one sees only the sender and undisclosed-recipients:;.
  • Reply-all reaches the sender only, not the group.

That is the visible cost of SES accepting the message at all, and it’s the industry-standard handling of large recipient sets. If mutual visibility matters for a particular send, keep it under the cap.

Your own records are unaffected. The console mail list, the message detail, and share links read the stored recipient set from the database, not the transmitted headers — so you still see everyone who received it, even though the wire headers say undisclosed-recipients:;.