How sandbox mode works in the sending API
The Mailyte team · · 5 min read

Every team that sends email from an application has, at some point, sent a test message to a real customer. Usually it's a seeded database row that turned out to hold a live address. Occasionally it's the whole list.
Sandbox mode exists to make that impossible without changing how your code behaves.
The design goal: identical behaviour, no delivery
A sandbox that behaves differently from production is worse than no sandbox, because it teaches you the wrong thing. So a sandboxed key does everything a live key does:
- The API call is authenticated and validated the same way.
- The message is rendered — templates, merge variables, tracking injection, the lot.
- It gets a message ID, and it appears in your logs.
- Suppression checks still run, so you can test that a suppressed address is refused.
The only difference is the last hop. Nothing is handed to the mail server for delivery, so nothing leaves our infrastructure.
Per key, not per environment variable
Sandbox is a property of the API key, not a flag in your request body or a environment variable on your side. That matters:
- A key that was minted for staging cannot accidentally send real mail, whatever your code does with it.
- Nobody can flip a request into production by editing a payload.
- Rotating a compromised staging key can't affect production sending.
Create separate keys for development, staging and production. Mark the first two as sandbox and forget about it.
What to actually test with it
The valuable tests aren't "does the API return 200". They're the ones that catch what breaks in production:
- Rendering with real data. Merge variables against a real contact record, not a fixture — that's where a missing field surfaces as a literal
{{ first_name }}in a subject line. - Suppression handling. Add an address to the suppression list and confirm your code handles the refusal rather than treating it as a hard failure.
- Webhook handling. Point your staging webhook endpoint at the sandbox key and confirm your handler is idempotent, because delivery events retry.
When you're ready
Swap the key. Nothing else in your integration changes — that's the point.


