Testing with Sandbox Mode

This guide shows you how to test a Sent integration against the live API without sending messages: in your test suite, in CI, and when reproducing a production failure. It assumes you have a working integration and a valid API key. For the exact behavior of the sandbox field and the endpoints that support it, refer to the sandbox mode reference.

Assert Against Real Responses

Add "sandbox": true to the request body in your integration tests. The API authenticates and validates the request, then returns the production response schema with sample data, so your assertions exercise the real contract while nothing is sent.

// tests/messages.sandbox.spec.ts
import { describe, expect, it } from 'vitest';

const BASE_URL = 'https://api.sent.dm';

describe('POST /v3/messages (sandbox)', () => {
  it('accepts a valid payload without sending anything', async () => {
    const response = await fetch(`${BASE_URL}/v3/messages`, {
      method: 'POST',
      headers: {
        'x-api-key': process.env.SENT_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        sandbox: true, // validated, never sent
        to: ['+14155550123'],
        channel: ['sms'],
        template: {
          id: '7ba7b820-9dad-11d1-80b4-00c04fd430c8',
          parameters: { name: 'Test' },
        },
      }),
    });

    const body = await response.json();

    expect(response.status).toBe(202);
    expect(response.headers.get('X-Sandbox')).toBe('true');
    expect(body.success).toBe(true);
    expect(body.data.status).toBe('QUEUED');
    expect(body.data.recipients[0].message_id).toBeDefined();
  });
});
# tests/test_messages_sandbox.py
import os

import requests

BASE_URL = "https://api.sent.dm"


def test_send_accepts_valid_payload_without_sending():
    response = requests.post(
        f"{BASE_URL}/v3/messages",
        headers={"x-api-key": os.environ["SENT_API_KEY"]},
        json={
            "sandbox": True,  # validated, never sent
            "to": ["+14155550123"],
            "channel": ["sms"],
            "template": {
                "id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
                "parameters": {"name": "Test"},
            },
        },
    )

    body = response.json()

    assert response.status_code == 202
    assert response.headers["X-Sandbox"] == "true"
    assert body["success"] is True
    assert body["data"]["status"] == "QUEUED"
    assert body["data"]["recipients"][0]["message_id"]

The same pattern works for any endpoint in the supported endpoints table; sandbox delete requests, for example, return 204 without deleting anything.

Sandbox responses contain sample data. A simulated message_id references no stored message, so do not follow it up with a GET /v3/messages/{id} assertion; that returns 404.

Run Sandbox Tests in CI

Make the flag injectable so the same suite runs simulated in CI and live where you explicitly choose to. Read one environment variable in a shared helper:

// tests/helpers/payload.ts
export function withSandbox<T extends object>(payload: T) {
  // CI sets SENT_SANDBOX=true; a live run leaves it unset.
  return { ...payload, sandbox: process.env.SENT_SANDBOX === 'true' };
}
# tests/helpers.py
import os


def with_sandbox(payload: dict) -> dict:
    # CI sets SENT_SANDBOX=true; a live run leaves it unset.
    return {**payload, "sandbox": os.environ.get("SENT_SANDBOX") == "true"}

Then set the variable in the pipeline:

# .github/workflows/test.yml
- name: Integration tests
  env:
    SENT_API_KEY: ${{ secrets.SENT_API_KEY }}
    # The helper reads this and adds sandbox: true to every request,
    # so the run sends no messages and consumes no balance.
    SENT_SANDBOX: "true"
  run: npm run test:integration

Reproduce a Production Failure

To debug a failed request without risking another live send, replay the exact production payload with the sandbox flag added:

const failedPayload = {
  to: ['+14155550123'],
  template: { id: '7ba7b820-9dad-11d1-80b4-00c04fd430c8' },
};

const response = await fetch('https://api.sent.dm/v3/messages', {
  method: 'POST',
  headers: {
    'x-api-key': process.env.SENT_API_KEY!,
    'Content-Type': 'application/json',
  },
  // Same payload as production, plus the sandbox flag.
  body: JSON.stringify({ ...failedPayload, sandbox: true }),
});

console.log(response.status, await response.json());
import os

import requests

failed_payload = {
    "to": ["+14155550123"],
    "template": {"id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8"},
}

response = requests.post(
    "https://api.sent.dm/v3/messages",
    headers={"x-api-key": os.environ["SENT_API_KEY"]},
    # Same payload as production, plus the sandbox flag.
    json={**failed_payload, "sandbox": True},
)

print(response.status_code, response.json())

Sandbox mode reproduces authentication and validation failures only. Failures that happen after a request is accepted, such as delivery errors, never occur in sandbox mode because execution is skipped.

Keep Sandbox Mode Out of Production

sandbox is a per-request body field, not an account setting or a key type, so the same running service can emit both simulated and live requests. If you toggle it by environment, make the production value an explicit false:

// config.ts
// Only production sends for real.
export const sandbox = process.env.NODE_ENV !== 'production';
# config.py
import os

# Only production sends for real.
SANDBOX = os.environ.get("ENVIRONMENT") != "production"

A sandbox: true that leaks into production silently drops real messages: the API returns 202 with "status": "QUEUED" as if the send were accepted, but nothing is delivered. Add a go-live check that asserts the toggle is off, and confirm production responses do not carry the X-Sandbox header.

Verify the Result

Every simulated response carries the X-Sandbox: true header, including error responses. Assert it in any test that must not send; if it is missing, the request executed for real.

Troubleshooting

SymptomLikely causeFix
No X-Sandbox header on the responsesandbox was not the JSON boolean true, or the endpoint does not support itSend "sandbox": true in the request body and check the supported endpoints table.
Tests fail with 401Missing or invalid API keySandbox requests still authenticate. Set SENT_API_KEY in the test environment.
Follow-up reads return 404Sandbox responses contain generated sample IDsAssert on the sandbox response itself, not on stored state.

On this page