Sandbox Mode is a safe testing capability for the email send APIs.
Sandbox Mode is a safe testing capability for the email send APIs. When you append a sandboxMode parameter to a send request, Aurora SendCloud runs the same authentication, requestId idempotency check, and full parameter validation as a real send — and then stops before any email is delivered. No message leaves the system, no quota is consumed, and no statistics are recorded.
Sandbox Mode gives you two options. Validate mode lets you confirm that a request would be accepted before you go live. Webhook mode additionally generates simulated delivery events — delivered, opened, clicked, bounced — and pushes them to your registered WebHook URL through the same pipeline used by production events. This lets you build and test your entire callback integration, including failure paths that are difficult to trigger with real traffic.
How It Works
Submit with sandboxMode: Send a request to /api/mail/send, /api/mail/sendtemplate, or /api/mail/sendcalendar with sandboxMode=validate or sandboxMode=webhook appended.
For detailed request parameters and response formats, please refer to the API documentation for the following email-sending API:
- Basic Send (
/api/mail/send) - Send Template Email (
/api/mail/sendtemplate) - Send Calendar Email (
/api/mail/sendcalendar)
Full Validation Runs: The request passes through the complete production chain — API_USER authentication, requestId idempotency, sender, recipients, content, attachments, labels, template, and calendar permission checks.
Delivery Is Skipped: After validation succeeds, the request is truncated. No email is sent to any recipient, no address-list task is created, and nothing enters delivery statistics or email status queries.
Events Are Simulated (webhook mode only): Aurora SendCloud generates simulated events with the same payload structure as production WebHook events and pushes them to the webhook URL configured in your account. Your parsing and signature-verification code works unchanged.
Result Is Returned: The synchronous response tells you exactly what would have happened — simulated emailIdList for your recipients, or a simulated maillistTaskId for address-list sends.
Modes
| Mode | Parameter | Behavior | Typical Use |
|---|---|---|---|
| Validate | sandboxMode=validate | Full validation, no delivery, simulated IDs in response | Pre-launch integration testing, recipient parsing checks |
| Webhook | sandboxMode=webhook | No delivery, simulated WebHook events pushed to your URL | WebHook integration development, failure-path testing, regression testing |
Omitting sandboxMode performs a normal real send — the parameter only ever enables sandbox behavior.
Validate Mode
Validate mode returns everything a real send would return, with simulated identifiers:
curl -X POST 'https://api.aurorasendcloud.com/api/mail/send' \
-d 'apiUser={apiUser}' \
-d 'apiKey={apiKey}' \
-d '[email protected]' \
-d '[email protected];[email protected]' \
-d 'subject=Sandbox validate test' \
-d 'html=<p>Hello</p>' \
-d 'sandboxMode=validate'{
"result": true,
"statusCode": 200,
"message": "Request successful",
"info": {
"sandbox": true,
"sandboxMessage": "Sandbox validation successful, no email delivered",
"recipientCount": 2,
"size": 1024,
"emailIdList": [
"[email protected]",
"[email protected]"
]
}
}With useAddressList=true, the response reports the real member count of your lists together with simulated task IDs:
{
"result": true,
"statusCode": 200,
"message": "Request successful",
"info": {
"sandbox": true,
"sandboxMessage": "Sandbox validation successful, no address-list task created",
"addressListCount": 1,
"estimatedRecipientCount": 42,
"maillistTaskId": [-123456789]
}
}respEmailId=false removes emailIdList from the response, matching the behavior of real sends.
Webhook Mode
Webhook mode accepts an additional sandboxEvents parameter that selects which events to simulate:
curl -X POST 'https://api.aurorasendcloud.com/api/mail/send' \
-d 'apiUser={apiUser}' \
-d 'apiKey={apiKey}' \
-d '[email protected]' \
-d '[email protected]' \
-d 'subject=Sandbox webhook test' \
-d 'html=<p>Hello</p>' \
-d 'sandboxMode=webhook' \
-d 'sandboxEvents=request,deliver,open,click'{
"result": true,
"statusCode": 200,
"message": "Request successful",
"info": {
"sandbox": true,
"sandboxMode": "webhook",
"sandboxMessage": "Webhook sandbox events accepted, no email delivered",
"sandboxEvents": ["request", "deliver", "open", "click"],
"sandboxEvent": true,
"emailIdList": ["[email protected]"]
}
}A successful response means the events were accepted. The actual WebHook callbacks arrive asynchronously at your registered URL, with the same payload structure, token, and signature as production events — see the WebHook reference for payload details.
Identifying Simulated Events
Every simulated event carries a marker in the userHeaders field, so your receiver can distinguish sandbox traffic from production traffic:
{
"event": "deliver",
"recipient": "[email protected]",
"emailId": "[email protected]",
"timestamp": 1780000000,
"token": "...",
"signature": "...",
"userHeaders": "{\"X-Sandbox\":\"true\"}"
}userHeaders is a JSON-encoded string. Parse it and check the X-Sandbox key — it is "true" on every sandbox event, on every event type, and production events do not include it:
import json
def is_sandbox_event(user_headers: str) -> bool:
headers = json.loads(user_headers)
return headers.get("X-Sandbox") == "true"Filter or tag sandbox traffic with this check — for example, route simulated events to a test table so they never mix with production data.
Supported Events
| Event Code | Event Name | Event Value (event) | Description |
|---|---|---|---|
18 | Requested | request | Email send request event, one per request |
1 | Delivered | deliver | Message successfully delivered to the recipient's mail server |
11 | Open | open | Recipient opened the email |
10 | Click | click | Recipient clicked a link in the email |
12 | Unsubscribe | unsubscribe | Recipient clicked the unsubscribe link |
3 | Spam Report | report_spam | Recipient reported the email as spam |
4 | Invalid Email (Deprecated) | invalid | Invalid email address event, deprecated soon |
81 | Suppressed | suppressed | Suppressed messages due to past suppression |
83 | Hard Bounce | hard_bounce | Permanent failures from invalid addresses |
7 | Soft Bounce | soft_bounce | Temporary delivery failure event |
sandboxEvents accepts event names or numeric event codes, in any case, mixed freely — for example sandboxEvents=deliver,11 is equivalent to sandboxEvents=deliver,open. deliver also answers to the alias delivered, and report_spam to reported_spam. Duplicates are removed, keeping the first occurrence. If sandboxEvents is omitted, the default is request,deliver.
There is no bounce event. Bounces are split into soft_bounce (temporary) and hard_bounce (permanent); passing bounce or event code 5 returns an error.
Event Combination Rules
Simulated events follow the semantics of real delivery, so some combinations are rejected with error code 40704:
- At most one terminal failure event per request.
invalid,suppressed, andhard_bounceare terminal failure events; at most one of them may appear in a single request. deliveris mutually exclusive with terminal failure events. An email either gets delivered or it fails — combiningdeliverwith any ofinvalid/suppressed/hard_bounceis rejected, and the error message names every conflicting event (for examplemutually exclusive sandbox events: deliver+invalid).soft_bouncecombines freely. A temporary failure can appear alone (retry in progress) or together withdeliver(retry eventually succeeded).requestcombines with everything.
The largest valid combination is: request,deliver,soft_bounce,open,click,unsubscribe,report_spam.
Sending with Address Lists
When useAddressList=true and sandboxMode=webhook, events are generated from the real members of your address lists:
- The
requestevent is one per request and carries a simulated task ID and the estimated recipient count. - Every other event is generated for sampled list members — subscribed members taken in join order, up to the first 1,000 across all listed lists — so callback recipients are real member addresses.
- Unsubscribed members are never sampled.
- If a list contains no subscribed members, there are no events to publish and the request is rejected with
40704(no sandbox webhook events to publish).
For normal-recipient sends, each non-request event is generated once per recipient, and the total event count is capped at 100 per request (40704 sandbox events exceed maxEventsPerRequest when exceeded).
Prerequisites
Use a test API user: Sandbox Mode is available to test API users (categoryType=0). Passing sandboxMode with a regular API user returns 40704 (sandboxMode is only available for test API users).
Register a webhook URL (webhook mode): Every event you request must have a webhook URL configured for your account — either the default category or the category matching the API user. Log in to the Aurora SendCloud platform, navigate to the WebHook settings page and add the URL, or call the WebHook API:
curl -X POST 'https://api.aurorasendcloud.com/api/webhook/add' \
-d 'apiUser={apiUser}' \
-d 'apiKey={apiKey}' \
-d 'event=1,3,4,7,10,11,12,18,81,83' \
-d 'url=http://{your-server}/webhook'The event parameter takes the numeric event codes from the table above; this example covers every sandbox-supported event. A missing URL for a requested event returns 40706 (no webhook url for sandbox event: open).
Have the feature enabled: Sandbox Mode is disabled by default. If the feature has not been enabled for your account or region, the API returns 40018 — contact Aurora SendCloud support to enable it.
Sandbox Mode Responses
All sandbox responses share the standard business envelope: result, statusCode, message, and info.
Validate mode, normal recipients:
| parameter | type | description |
|---|---|---|
| info.sandbox | boolean | always true in sandbox responses |
| info.sandboxMessage | string | human-readable sandbox outcome |
| info.recipientCount | int | number of resolved recipients (from to, cc, bcc, or xsmtpapi.to) |
| info.size | long | request size in bytes |
| info.emailIdList | string[] | simulated email IDs, one per recipient; removed when respEmailId=false |
Validate mode, address lists:
| parameter | type | description |
|---|---|---|
| info.addressListCount | int | number of address lists in the request |
| info.estimatedRecipientCount | int | real member count across the listed lists |
| info.maillistTaskId | long[] | simulated task IDs (negative values) |
Webhook mode:
| parameter | type | description |
|---|---|---|
| info.sandboxMode | string | webhook |
| info.sandboxEvents | string[] | resolved event names, in request order |
| info.sandboxEvent | boolean | always true |
| info.emailIdList | string[] | simulated email IDs, unique per recipient; present for address-list sends when the lists contain subscribed members |
| info.maillistTaskId | long[] | simulated task IDs (address-list sends) |
Simulated emailId values follow the production format ({timestamp}_USERID_{apiUserId}_{seq}.{host}{index}${recipient}), so they are safe inputs for testing your callback parsing. They do not exist in delivery records — querying them through the email status API returns no result.
Rate Limits
Sandbox Mode requests are protected by dedicated rate limits, separate from your real sending quota. When a limit is reached the API returns 50000 (interface frequency limited). The default limits are 100 requests per minute per user and 60 per minute per API user for validate mode, with tighter limits (30 and 20) for webhook mode.
Error Codes
| statusCode | Occurs when | Resolution |
|---|---|---|
40704 | sandboxMode is neither validate nor webhook | Fix the parameter value |
40704 | sandboxEvents passed without sandboxMode=webhook | Only pass sandboxEvents in webhook mode |
40704 | Unknown event name or code (including bounce / 5) | Use an event from the supported-events table |
40704 | Mutually exclusive event combination | Follow the combination rules above |
40704 | sandboxMode used with a non-test API user | Switch to a test API user |
40704 | Empty event list, or address list has no subscribed members | Pass valid events; add subscribed members to the list |
40704 | Event count exceeds the per-request cap (normal recipients) | Reduce events or recipients |
40018 | Sandbox Mode is not enabled for the account or region | Contact Aurora SendCloud support |
40706 | A requested event has no webhook URL | Register a webhook URL for that event |
40874 | useAddressList is neither true nor false in webhook mode | Fix the parameter value |
40901 | Event backup failed on the server; request not accepted | Infrastructure issue — retry after the fault is resolved |
50000 | Sandbox Mode rate limit reached | Slow down, or request higher limits |
All other errors — authentication, requestId, mail parameters, templates, calendars, attachments — use the same codes and messages as the base send APIs.
Best Practices
Deduplicate retried events: Event delivery is retried automatically when your server is unavailable, and retries may produce duplicate events. Deduplicate callbacks by event + emailId + timestamp + recipient.
Correlate retries with sendRequestId: Set sendRequestId (or the X-Request-ID header) on each sandbox request. The request event's emailId changes when a request is retried, so the request ID is the stable correlation key.
Prefer suppressed and hard_bounce over invalid: invalid is deprecated and will be removed. New integrations should simulate failure paths with suppressed or hard_bounce.
Test the full lifecycle in one request: Combine request, deliver, soft_bounce, open, click, unsubscribe, and report_spam in a single call to exercise your entire callback pipeline, then use single failure events (suppressed, hard_bounce) for targeted failure handling.
Expect asynchronous callbacks: A 200 response means events were accepted, with delivery to your webhook following asynchronously. Do not poll the email status API for sandbox emailIds — sandbox events do not enter delivery records or statistics.
FAQ
Does Sandbox Mode send real emails?
No. In both modes, validation runs against the exact production rules and then delivery is skipped. No message is sent to any recipient.
Do Sandbox Mode requests consume my sending quota?
No. Sandbox Mode requests have their own rate limits and do not consume sending quota or appear in delivery statistics.
Are simulated webhook events identical to production events?
Yes. They carry the same payload structure, including token and signature, so your existing parsing and signature-verification code works without changes. The only addition is the X-Sandbox marker in userHeaders — see Identifying Simulated Events.
Why is there no bounce event?
Bounces are split into soft_bounce (temporary, retryable) and hard_bounce (permanent). Passing bounce or event code 5 returns 40704.
Which endpoints support Sandbox Mode?
/api/mail/send and /api/mail/sendtemplate (POST and GET) and /api/mail/sendcalendar (POST). All other request parameters are unchanged — Sandbox Mode only adds sandboxMode and sandboxEvents.
