---
title: Sandbox vs Live
path: concepts/sandbox-vs-live
status: published
---

# Sandbox vs Live

Every ScaiSend send runs in one of two modes: **live** (actually delivered) or **sandbox** (validated, recorded, but not delivered). This page describes when each applies, how to force sandbox, and how to verify what happened.

## The two mechanisms

Sandbox mode turns on in two independent ways:

1. **The request uses a test API key** (`sg_test_*`). Every request made with the key is sandbox. There is no way to override this to live.
2. **The request body sets** `mail_settings.sandbox_mode.enable: true`. Per-request override; works on live and test keys alike.

Either mechanism is sufficient. If both are absent, the send goes live.

| Key type | `sandbox_mode.enable` | Result |
|----------|-----------------------|--------|
| `sg_live_*` | absent or `false` | Live send |
| `sg_live_*` | `true` | Sandbox |
| `sg_test_*` | absent or `false` | Sandbox |
| `sg_test_*` | `true` | Sandbox |

## What happens in each mode

### Live mode

- Request is validated (schema, size, attachment count, template_id format, etc.).
- If `From:` domain isn't verified, the request fails synchronously.
- Message is persisted with status `QUEUED`.
- Worker service picks it up, renders the template, applies tracking, builds MIME.
- SMTP service picks it up, resolves MX, DKIM-signs, connects, delivers.
- Events (`processed`, `delivered`, `bounce`, `deferred`, `open`, `click`) are recorded and fanned out to webhooks.
- Status progresses: `QUEUED` → `PROCESSING` → `SENDING` → `DELIVERED` (or `BOUNCED` / `FAILED`).

### Sandbox mode

- Request is validated the same way as live.
- **Domain verification is not checked.** You can send from an unverified domain in sandbox.
- Message is persisted with status `SANDBOX`.
- **Nothing is queued to the SMTP service.** The Worker doesn't render the template; no MIME is built.
- **No events are fanned out.** You won't receive `delivered`, `bounce`, etc.
- Response shape is identical to live — same `message_id`, same HTTP status.

Sandbox messages are visible via `GET /v3/messages` and `GET /v3/messages/{id}` with status `sandbox`.

## Forcing sandbox on a live key

```bash
curl -X POST https://scaisend.scailabs.ai/v3/mail/send \
  -H "Authorization: Bearer $SCAISEND_LIVE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "personalizations": [{"to": [{"email": "test@example.com"}]}],
    "from": {"email": "hello@mail.example.com"},
    "subject": "Sandbox check",
    "content": [{"type": "text/plain", "value": "This will not be delivered."}],
    "mail_settings": {
      "sandbox_mode": {"enable": true}
    }
  }'
```

```python
import os, httpx

resp = httpx.post(
    "https://scaisend.scailabs.ai/v3/mail/send",
    headers={"Authorization": f"Bearer {os.environ['SCAISEND_LIVE_KEY']}"},
    json={
        "personalizations": [{"to": [{"email": "test@example.com"}]}],
        "from": {"email": "hello@mail.example.com"},
        "subject": "Sandbox check",
        "content": [{"type": "text/plain", "value": "This will not be delivered."}],
        "mail_settings": {"sandbox_mode": {"enable": True}},
    },
)
print(resp.status_code, resp.json())
```

```typescript
const resp = await fetch("https://scaisend.scailabs.ai/v3/mail/send", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SCAISEND_LIVE_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    personalizations: [{ to: [{ email: "test@example.com" }] }],
    from: { email: "hello@mail.example.com" },
    subject: "Sandbox check",
    content: [{ type: "text/plain", value: "This will not be delivered." }],
    mail_settings: { sandbox_mode: { enable: true } },
  }),
});
```

## Using test keys in CI

Create a dedicated test key for CI:

```bash
curl -X POST https://scaisend.scailabs.ai/v3/api_keys \
  -H "Authorization: Bearer $SCAISEND_JWT" \
  -H "Content-Type: application/json" \
  -d '{"name": "ci-tests", "environment": "test", "scopes": ["mail.send"]}'
```

Store the returned `sg_test_*` key as a CI secret. Your test suite can call `/v3/mail/send` against a real ScaiSend instance without sending real mail — request validation, template rendering, and response shape all behave exactly as they would in production.

This is the pattern to prefer over per-request `sandbox_mode`. Environment isolation via the key type is a stronger guarantee than a body flag that a misconfigured environment variable could flip.

## Confirming a send was sandboxed

```bash
curl https://scaisend.scailabs.ai/v3/messages/msg_01HXYZ \
  -H "Authorization: Bearer $SCAISEND_API_KEY"
```

The `status` field is `sandbox`. The `events[]` array is empty (or contains only `processed` if your deployment records that even for sandbox — varies by version).

```json
{
  "id": "msg_01HXYZ",
  "status": "sandbox",
  "from_email": "hello@mail.example.com",
  "to_emails": ["test@example.com"],
  "subject": "Sandbox check",
  "created_at": "2026-04-23T10:00:00Z",
  "events": []
}
```

## What sandbox does not do

- **No rate limits are enforced differently.** Sandbox sends count against your tenant's rate limit.
- **No statistics are incremented.** `GET /v3/stats` ignores sandbox messages. You won't see them in `processed` counts.
- **No webhooks fire.** Your production webhook endpoint won't be bothered by CI traffic.
- **Attachments are not stored.** ScaiSend accepts them (and validates size and type) but doesn't persist them to S3.

## When to use each

| Scenario | Mechanism |
|----------|-----------|
| CI pipeline, end-to-end integration tests | Test key (`sg_test_*`) |
| Staging environment | Test key |
| Local developer laptops | Test key, one per developer |
| Production smoke test immediately after deploy | Live key with `sandbox_mode.enable: true` |
| Debugging a send failure in production without delivering | Live key with `sandbox_mode.enable: true` |
| Production traffic | Live key |

## What's next

- [Authentication](authentication) — creating test keys.
- [Your First Integration](../tutorials/first-integration) — recommends test keys for CI.
- [Sending Mail](../tutorials/sending-mail) — the full `/v3/mail/send` request shape including `mail_settings`.
