skip to content

Your service must push a 400,000-message nightly campaign through Amazon SES, but the account's maximum send rate is 50 messages per second. How do you design the sender?

level: seniorimportance: should knowfreq 44%

answer

  1. two limits, not one
  2. rolling window, not midnight reset
  3. excess is rejected, never queued
  4. the rate is shared with transactional mail
  5. raise the quota before the campaign

basics

~20 s

Treat the two SES limits as separate constraints: a rolling 24-hour cap on volume and a per-second send rate. Get the daily quota raised ahead of time, buffer the work in a queue, and have workers self-throttle below the send rate with backoff and jitter on throttling errors.

solid answer

~50 s

SES enforces two independent limits: `Max24HourSend`, a rolling 24-hour message budget, and `MaxSendRate`, a per-second ceiling. Excess is **rejected, not queued** — SES returns a throttling error and that message is gone unless you kept it. So first, check the real numbers with the SESv2 `GetAccount` call and request a quota increase well before campaign night, because 400,000 messages will exceed a typical daily budget and increases are granted gradually against your reputation. Second, decouple: enqueue the recipient list (SQS is the usual choice) so a rejected send is retried rather than lost. Third, give the worker pool a shared rate limiter set deliberately *below* the published rate — the limit is account-wide, so transactional password-reset mail is competing with your campaign for the same budget. Retry throttles with exponential backoff and jitter, alarm on sustained throttling, and record message IDs so a retry after an ambiguous failure does not double-send.

code

bash · 3 lines
bash
# The three numbers this design turns on, before campaign night
aws sesv2 get-account --query 'SendQuota' 
# -> { "Max24HourSend": 50000.0, "MaxSendRate": 50.0, "SentLast24Hours": 1200.0 }

go deeper

for a junior

Know that SES caps both how many messages you can send in 24 hours and how many per second, and that going over gets an error rather than being queued for later.

for a middle

Explain the difference between the rolling 24-hour budget and the per-second rate, where to read both, and why a durable queue in front of the sender turns a throttled send into a retry rather than a lost message.

for a senior

Demonstrate production judgment: self-throttle below the ceiling with backoff and jitter, reserve headroom so campaigns cannot starve transactional mail, deduplicate on your side, and alarm on quota consumption before it is exhausted.

for a principal

Own the isolation and governance call — whether marketing and transactional mail share an account and quota at all, how sending budget is allocated between teams, and what reputation damage from one campaign is allowed to cost the rest of the business.

## The two limits, and why people conflate them SES publishes two numbers per account per Region, both visible in the `SendQuota` returned by the SESv2 `GetAccount` operation: - **`Max24HourSend`** — how many messages you may send in a *rolling* 24-hour window. Rolling matters: there is no midnight reset that frees the whole budget at once. `SentLast24Hours` tells you how much of it you have consumed. - **`MaxSendRate`** — the maximum messages per second. A design can satisfy one and violate the other. 400,000 messages at 50/s is a little over two hours of sending, which respects the rate but will blow straight through a daily budget that is smaller than 400,000. Answering "I'll just run it at 50 a second" without checking the daily cap is the trap in this question. ## Rejection, not queueing When you exceed the rate, SES **rejects the call** — the SESv2 API surfaces it as a throttling exception (`TooManyRequestsException`). It does not buffer your excess for later. If the only copy of that recipient's work item was the in-flight API call, the message is lost. This is why the sender must be built around durable work items rather than a `for` loop over a list. ## The architecture **Buffer the work.** Fan the recipient list out into a queue — SQS is the conventional choice — so each recipient is a durable work item. A throttled send simply fails and the item is retried; a repeatedly failing item lands in a dead-letter queue for inspection rather than vanishing. **Throttle on purpose, below the ceiling.** Do not discover the limit by hitting it. Give the worker pool a shared token bucket sized at some fraction of `MaxSendRate` — often 70–80% — because: - The limit is **account-wide and Region-wide**. Your campaign shares it with every transactional email the platform sends. Password resets, receipts and 2FA codes are the mail that must never be delayed, and a campaign that consumes the entire rate will starve them. Reserving headroom for transactional traffic (or isolating campaign traffic in a separate account) is the senior-grade part of this answer. - Concurrency is the practical control. Rate ≈ concurrency ÷ per-call latency, so capping worker concurrency is usually how the token bucket is actually implemented. **Back off correctly.** On a throttling error, retry with exponential backoff **plus jitter**. Without jitter a fleet of workers retries in lockstep and re-throttles together in a thundering herd. Distinguish error classes: throttling is retryable; a rejected message (bad address, unverified identity) is not, and retrying it forever just burns quota. **Spread across time.** If the daily budget genuinely cannot cover the campaign, the campaign changes shape: split it across several nights, or segment recipients. A quota increase request should be filed days ahead with the volume and consent story, because SES raises limits progressively as clean volume builds — you cannot get a 10× increase on the afternoon of the send. ## The idempotency problem nobody mentions A retry after an *ambiguous* failure — a timeout where the request may have been accepted — can send the same message twice. SES has no request-level deduplication. The mitigation is on your side: persist a per-recipient send record keyed by campaign and recipient, write the returned message ID, and check that record before re-sending. Duplicate marketing mail is a complaint generator, and complaints are what actually cost you your sending reputation. ## What to watch while it runs - `SentLast24Hours` against `Max24HourSend`, alarmed at a threshold well below the cap, so you learn about exhaustion before the transactional path starts failing. - Throttling error rate from the workers. A low steady trickle means your limiter is tuned near the edge; a spike means something else in the account started sending. - Queue depth and DLQ depth, which tell you whether the campaign will finish inside its window. - Bounce and complaint rates, because a large campaign is exactly when reputation damage happens — and because SES can pause an account's sending outright when those rates go bad, which turns a campaign problem into a transactional outage. ## Summarising the answer Raise the quota in advance, buffer in a queue, self-throttle below the account-wide rate with reserved headroom for transactional mail, back off with jitter, deduplicate on your side, and alarm on quota consumption and throttling rather than on failed campaigns.

  • Why should the campaign run below the published maximum send rate rather than at it?
    Because the rate is account-wide and Region-wide. Transactional mail — password resets, 2FA codes, receipts — shares the same budget, and a campaign at 100% of the rate starves it. Reserve headroom, or isolate campaign sending in a separate AWS account so a marketing burst can never delay the mail customers are actively waiting for.
  • A network timeout leaves you unsure whether a send succeeded. What stops the retry from double-sending?
    Nothing in SES — there is no request-level deduplication. You need a per-recipient send record keyed by campaign and recipient, written with the returned message ID, and checked before re-sending. Duplicate marketing mail drives complaints, and complaint rate is what actually threatens your sending reputation and therefore your transactional mail.
  • Your daily quota is smaller than the campaign. What are the options?
    Request an increase days in advance with volume and consent details, since SES raises limits progressively against clean sending history rather than on demand. Failing that, reshape the campaign: split it across several nights, segment the audience, or move campaign traffic to a separate account with its own quota so the transactional budget stays untouched.
  • What tells you during the run that the campaign is heading for trouble?
    Three signals: SentLast24Hours closing on Max24HourSend, alarmed well below the cap; the workers' throttling error rate, where a spike means something else started sending into the same account; and bounce and complaint rates, because SES can pause sending for an account whose rates go bad, turning a campaign issue into a transactional outage.

saying these in an interview costs you the question

  • Treats the daily cap and send rate as one limit
  • Assumes SES queues whatever exceeds the rate
  • Retries throttles without jitter, causing a herd
  • Ignores that transactional mail shares the quota
  • Plans the quota increase for the day of the campaign

context