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:integrationReproduce 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
| Symptom | Likely cause | Fix |
|---|---|---|
No X-Sandbox header on the response | sandbox was not the JSON boolean true, or the endpoint does not support it | Send "sandbox": true in the request body and check the supported endpoints table. |
Tests fail with 401 | Missing or invalid API key | Sandbox requests still authenticate. Set SENT_API_KEY in the test environment. |
Follow-up reads return 404 | Sandbox responses contain generated sample IDs | Assert on the sandbox response itself, not on stored state. |
Related
Testing & Debugging
Test and debug your Sent integration by validating sends with sandbox mode, unit-testing webhook handlers, verifying signatures, and tracing failures.
Webhooks & Events Overview
Receive real-time message delivery notifications, inbound messages, and template approval events via webhooks, with guides for setup, security, and retries