# Sandbox & test numbers

Source: https://joinsimplesms.com/docs/sandbox
Index: https://joinsimplesms.com/llms.txt

Test keys (`ssms_sk_test_`) run against a fully simulated environment: no
carrier traffic, no charges, and nothing real ever sent. Every endpoint works,
so you can build your whole integration, including webhooks, before going
live.

## Your sandbox number

Signup provisions a sandbox number in the reserved `+1 500-555-XXXX` range.
It's the `from` for outbound tests and the `to` for simulated inbound. You can
"purchase" more from `GET /v1/numbers/available` + `POST /v1/numbers`; the
whole numbers API works in the sandbox.

## Your own phone

The console asks for your cell once, right after signup, and texts you a code.
Once it's verified, a test key delivers to **that one number for real**, so
your first API call makes your phone buzz. Every other destination stays
simulated. Real sandbox texts are capped at 10 a day and end with
`- via SimpleSMS sandbox`. The response is a normal message with `"test": true`
and an `X-Sandbox-Real-Delivery: true` header. Your phone also counts as your
first verified recipient when you go live on the free tier.

## Magic destination numbers

Send **to** these numbers to trigger fixed behaviors:

| Number | Behavior |
| --- | --- |
| `+15005550006` | Delivered: `message.sent` then `message.delivered`; the message is `delivered` by the time the send returns. Any other number behaves the same. |
| `+15005550013` | Delayed delivery: status `sent`, then really delivered at least 5 seconds later. Poll `GET /v1/messages/{id}` (it settles as soon as the delay has passed) or wait for the `message.delivered` webhook (within about a minute). Use it to test code that waits on delivery. |
| `+15005550001` | Stuck: status stays `queued` forever, no delivery event. |
| `+15005550002` | Failed, no reason given: `failure.code` `unknown`. |
| `+15005550007` | Failed: filtered as spam, `failure.code` `carrier_filtered`. |
| `+15005550008` | Failed: refused by the carrier, `failure.code` `carrier_rejected`. |
| `+15005550009` | Failed: number not in service, `failure.code` `invalid_number`. |
| `+15005550010` | Failed: a landline, `failure.code` `landline` (and `GET /v1/lookup` says `landline`). |
| `+15005550014` | Failed: phone unreachable, `failure.code` `unreachable`. |
| `+15005550011` | Opted out: `403 forbidden`, exactly as for a recipient who replied STOP. Nothing is written to your opt-out list. |
| `+15005550012` | Rate limited: `429 rate_limited` with a `Retry-After` header. Nothing is stored. |

Failed numbers return `201` with status `failed` and emit `message.sent`
then `message.failed`; the `failure` object and the event payload are the
same ones a real carrier failure produces (see [Errors](/docs/errors#delivery-failures)).
Every sandbox message has a full `timeline`, `segments`, `encoding`,
a `price` of 0, and `destination_carrier` from the sandbox lookup.

## Simulated inbound

`POST /v1/test/inbound` delivers a fake inbound SMS to one of your sandbox
numbers through the real pipeline: it appears in `GET /v1/messages` and emits
a `message.received` event. (Test keys only; live keys get a 403
`test_mode_only`.)

## Sandbox limits

A ceiling of 1,000 messages/day per account keeps the sandbox healthy. It's
not a product quota. If you legitimately hit it, tell us.
