In the AWS SES sandbox you can send at most 200 messages per 24 hours, at 1 message per second, and only to verified email addresses or domains (plus the SES mailbox simulator). Those limits apply separately in each AWS Region, and the 200 counts recipients rather than messages, according to the AWS production access docs and the sending quota docs.
Getting production access removes the recipient restriction and raises both quotas. Until then, this guide covers every sandbox limit, how SES counts against them, how to read your quota from code, how to stay under the rate, and what each limit error means.
Every SES sandbox limit at a glance
Some of these limits belong to the sandbox and go away in production. Others apply to every SES account, and people often blame the sandbox for them. The table separates the two.
| Limit | In the sandbox | After production access | Source |
|---|---|---|---|
| Who you can send to | Verified addresses and domains, plus the mailbox simulator | Anyone | Production access |
| Sending quota | 200 recipients per rolling 24 hours | Set for your use case, adjustable | Service quotas |
| Sending rate | 1 per second | Set for your use case, adjustable | Service quotas |
| Sending authorization | Neither you nor a delegate can send to unverified addresses | Normal | Production access |
| Account suppression list | Bulk actions and suppression list API calls disabled | Available | Production access |
| Custom verification email templates | Not available (ProductionAccessNotGranted) | Available | Identities |
| From, Source, Sender, Return-Path | Must be verified | Still must be verified | Production access |
| Recipients per message (To, CC, BCC) | 50 | 50, not adjustable | Service quotas |
| Message size | 40 MB (API v2 and SMTP), 10 MB (API v1), after encoding | Same | Service quotas |
| Verified identities | 10,000 per region | Same | Service quotas |
| Other SES API calls | 1 request per second (everything except the send operations) | Same | Service quotas |
Two rows catch people out. The sender row: production access lifts the recipient check, not the sender check, so your From domain needs to be verified either way. The last row: calls like GetAccount or CreateEmailIdentity are throttled at 1 per second on every account, so a script that checks identities in a tight loop will hit throttling even after you leave the sandbox.
How SES counts against the sandbox quota
It counts recipients, not messages. An email with 3 addresses in To and 2 in CC uses 5 of your 200. AWS also recommends calling SendEmail once per recipient, because a single call with several recipients is rejected as a whole if any part of it fails (sending quota docs).
The 24 hours roll. There is no midnight reset. On every send, SES looks at how many emails you sent in the previous 24 hours. A worked example:
| Time | Sent | Sent in the last 24 hours | Room left |
|---|---|---|---|
| Monday 09:00 | 150 | 150 | 50 |
| Monday 15:00 | 50 | 200 | 0 |
| Tuesday 08:59 | 1 (rejected) | 200 | 0 |
| Tuesday 09:01 | 50 (Monday 09:00 batch aged out) | 150 |
The mailbox simulator is free on the quota, not on the rate. Mail to @simulator.amazonses.com addresses doesn't count toward the daily quota or your bounce and complaint rates, but it is still limited by your send rate, and you pay for it like any other message (simulator docs).
Rejected messages still count. If SES accepts a message and then rejects it (for example, because its virus scan finds something), that message still counts toward your daily quota and you are billed for it.
Short bursts above 1 per second can work, sustained ones fail. AWS says you can exceed the rate briefly, but not for long, and that the rate SES actually accepts can be lower than your maximum.
Verified recipients: what counts
While you're sandboxed, every To, CC, and BCC address must be a verified identity in the region you send from. From the identity docs:
- Verify a domain to cover every address on it. A verified domain identity lets you send to any address at that domain. If your team's test inboxes are all on your company domain, verify the domain once in the sending region instead of adding people one by one. The DNS setup guide explains the DKIM records that verification needs.
- Email addresses are case sensitive. If you verified
Dev@example.com, sending todev@example.comfails the check. Domains are not case sensitive. - Verification links expire after 24 hours. If a teammate didn't click in time, choose Resend on the identity's page in the console.
- Verification is per region. An address verified in
us-east-1is unverified inus-west-2.
To add a test recipient from the CLI (create-email-identity), run it in the region your app sends from, then have the owner click the link in the email from no-reply-aws@amazon.com:
aws sesv2 create-email-identity \
--region us-east-1 \
--email-identity teammate@example.com
Verified identities are capped at 10,000 per region, but verifying real customers one by one is not a workaround for the sandbox. It is slow for them, and it hides the problem you actually need to solve: getting production access.
Read your quota from code
The sandbox banner in the console tells you very little. GetAccount returns whether production access is on, plus a SendQuota object with your daily maximum, your rate, and what you've used in the last 24 hours. A Max24HourSend of -1 means unlimited.
import { SESv2Client, GetAccountCommand } from "@aws-sdk/client-sesv2";
export async function sesHeadroom(region: string) {
const ses = new SESv2Client({ region });
const account = await ses.send(new GetAccountCommand({}));
const quota = account.SendQuota ?? {};
const max24h = quota.Max24HourSend ?? 0;
const sent = quota.SentLast24Hours ?? 0;
return {
region,
sandboxed: account.ProductionAccessEnabled === false,
maxPerSecond: quota.MaxSendRate ?? 0,
max24h,
sentLast24h: sent,
remaining: max24h === -1 ? Infinity : Math.max(0, max24h - sent),
};
}
// In the sandbox this prints something like:
// { region: "us-east-1", sandboxed: true, maxPerSecond: 1, max24h: 200, sentLast24h: 37, remaining: 163 }
console.log(await sesHeadroom("us-east-1"));
From a shell: aws sesv2 get-account --region us-east-1 --query SendQuota. In the console, the same numbers are on Account dashboard under Sending limits and Daily email usage (monitoring docs).
Call this before a batch, not before every message. GetAccount itself is limited to 1 request per second.
Stay under the send rate
When you exceed either limit, SES drops the message and doesn't retry it for you (quota error docs). The API returns a ThrottlingException with one of two messages, and they need different handling:
Maximum sending rate exceeded: you went faster thanMaxSendRate. Slow down and retry in a moment.Daily message quota exceeded: you've used the rolling 24-hour quota. Retrying in a tight loop won't help. AWS suggests waiting up to 10 minutes between retries, since capacity frees up as old sends age out of the window.
The AWS SDK retries throttling errors on its own, but by default it makes only 3 attempts in total (SDK retry behavior), which isn't enough to absorb a sustained rate limit. AWS's own advice is to pace your calls on the client side and back off when throttled. A small version with no extra dependencies:
import { SESv2Client, SendEmailCommand } from "@aws-sdk/client-sesv2";
const ses = new SESv2Client({ region: "us-east-1" });
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
type Message = { to: string; subject: string; text: string };
// Send one recipient per call, no faster than maxPerSecond.
export async function sendPaced(messages: Message[], maxPerSecond: number) {
const gapMs = Math.ceil(1000 / maxPerSecond);
for (const m of messages) {
const started = Date.now();
await sendWithBackoff(m);
const elapsed = Date.now() - started;
if (elapsed < gapMs) await sleep(gapMs - elapsed);
}
}
async function sendWithBackoff(m: Message, attempt = 0): Promise<void> {
try {
await ses.send(
new SendEmailCommand({
FromEmailAddress: "app@mail.example.com",
Destination: { ToAddresses: [m.to] },
Content: {
Simple: {
Subject: { Data: m.subject },
Body: { Text: { Data: m.text } },
},
},
})
);
} catch (err) {
const e = err as { name?: string; message?: string };
if (e.name !== "ThrottlingException") throw err;
// The daily quota won't clear in seconds. Stop and let a later job resume.
if (e.message?.includes("Daily message quota exceeded")) throw err;
if (attempt >= 5) throw err;
const wait = Math.random() * Math.min(20_000, 1000 * 2 ** attempt);
await sleep(wait);
return sendWithBackoff(m, attempt + 1);
}
}
Get maxPerSecond from sesHeadroom() above so the same code works in the sandbox (1 per second) and after your quota goes up. If several workers send from the same account and region, they share one rate, so divide it between them or send from a single queue.
For SMTP, the same limits show up as 454 Throttling failure: Maximum sending rate exceeded and 454 Throttling failure: Daily message quota exceeded. How your SMTP library reacts to a 454 varies, so check that it retries instead of dropping the message.
Test without spending the 200
The mailbox simulator works in the sandbox and doesn't count toward the daily quota, so use it for anything automated (CI, load tests, webhook handling):
| Address | Simulates |
|---|---|
success@simulator.amazonses.com | Delivered |
bounce@simulator.amazonses.com | Hard bounce (SMTP 550 5.1.1) |
ooto@simulator.amazonses.com | Out-of-office auto reply |
complaint@simulator.amazonses.com | Recipient marked it as spam |
suppressionlist@simulator.amazonses.com | Recipient on the suppression list |
Labels work too, so bounce+signup-test@simulator.amazonses.com lets you check that your bounce handler matches each bounce to the send that caused it. Save your 200 real sends for the checks a simulator can't do: rendering in Gmail and Outlook, and inbox versus spam placement.
Getting past the limits
There are two different requests, and they're easy to mix up:
- Leave the sandbox. This lifts the verified-recipient rule and replaces the 200 and 1 per second numbers with a quota set for your use case. You request it from the SES console or with
aws sesv2 put-account-details, once per region. AWS says it gives an initial response within 96 hours. The AWS SES production access guide covers the form, the CLI call, a use case description you can adapt, and what to do if the request is denied. - Raise your quota once you're out. AWS may raise quotas automatically if you send real production mail, regularly get close to your daily maximum without going over, and keep bounces and complaints low (quota increase docs). If that doesn't happen, open the Service Quotas console, pick the region, choose AWS services, then Amazon Simple Email Service (SES), and request an increase to the sending quota or sending rate. AWS aims to resolve these within 24 hours.
You can ask for a higher quota in the same request that takes you out of the sandbox, which saves a round trip if you already know your volume. If you're weighing whether SES is worth the setup at your volume, the free SES savings calculator compares what you pay your current provider with SES pricing.
Troubleshooting sandbox limit errors
| Error or symptom | What it means | Fix |
|---|---|---|
MessageRejected: Email address is not verified. The following identities failed the check in region ... | A recipient (or the sender) isn't verified in the region named in the error | Verify the address or its domain in that region, or check that the region in your SDK client matches the one you set up |
| Same error for an address you know is verified | Case mismatch, or it's verified in a different region | Compare the exact casing and the region; Dev@ and dev@ are different identities |
ThrottlingException: Maximum sending rate exceeded | More than 1 send per second (or your production rate) | Pace sends from one queue; back off and retry |
ThrottlingException: Daily message quota exceeded | 200 recipients in the last rolling 24 hours | Stop the batch, resume later, or move test traffic to the simulator |
454 Throttling failure: ... over SMTP | The same two limits, reported by the SMTP interface | Configure your SMTP client to retry 454 responses with a delay |
ProductionAccessNotGranted | You called a production-only feature, such as custom verification email templates | Request production access first |
| Suppression list bulk actions or API calls fail | Account-level suppression list management is disabled in the sandbox | Request production access; until then, remove bounced addresses from your own list |
Throttling on GetAccount or identity calls | Non-send SES API calls are limited to 1 per second on every account | Cache results and space out management calls |
| Approved, but sends still fail | Your app sends from a different region than the one approved | Check ProductionAccessEnabled in the exact region you send from |
If you connect a sandboxed account to Mailmark through bring-your-own-AWS, the sandbox badge in Settings tells you why. Inbox warming sends to outside inboxes, so warmup sends from a sandboxed account fail, and the warming page shows how many recent sends failed and the last error instead of reporting a healthy mailbox.
Once you're in production, the limits that matter are your bounce and complaint rates, not the 200 per day. AWS starts a review at a 5% bounce rate and a 0.1% complaint rate (sending review FAQ). Run lists through the free email list validator before the first send. The email deliverability guide covers the rest of the setup that keeps mail out of spam.
Frequently asked questions
How many emails can I send in the SES sandbox?
200 per rolling 24-hour period, at no more than 1 per second, and only to verified addresses or domains. The quota counts recipients, so one email to 5 people uses 5 of your 200. The limit applies separately in each AWS Region.
Can I send to every address on a domain while in the SES sandbox?
Yes. If you verify a domain identity in the region you send from, any address on that domain counts as a verified recipient. That is the easiest way to test with a whole team's inboxes without verifying each address.
Do mailbox simulator emails count toward the SES sandbox quota?
They don't count toward the 200 per day quota or your bounce and complaint rates, but they are still limited by your maximum send rate of 1 per second, and AWS bills them like any other email.
What is my SES sending quota after I leave the sandbox?
AWS sets it per account based on your use case, so there is no fixed number. Read it from GetAccount (SendQuota.Max24HourSend and SendQuota.MaxSendRate) in each region, and request more in the Service Quotas console if it is too low.
Are SES sandbox limits per region?
Yes. Sandbox status, quotas, and verified identities are all separate per AWS Region. Leaving the sandbox in us-east-1 does nothing for eu-west-1, and an address verified in one region is unverified in every other.