Dry Run
Dry Run lets you build and test against the API without sending a single real message. Turn it on
from the app and every POST /api/sms/send is validated, rate-limited, and logged exactly as a
real send would be — but Hiras never hands the message to the SIM. No text goes out, no load is
spent.
It’s the mode to develop against: wire up your integration, watch the responses, and only switch to real sending once everything looks right.
Turning it on
Dry Run is a host-wide switch in the app. While it’s on, every send is a dry run — there is no
per-request override. It persists across restarts, so remember to switch it off before you expect
real messages to go out. The mode field on GET /health and GET /api/diagnostics tells you
which mode the gateway is currently in.
What a dry-run response looks like
A dry run returns HTTP 200 with a body that mirrors a real success, but with three clear
tells — mode, smsSent, and status:
{
"success": true,
"mode": "dry-run",
"smsSent": false,
"messageId": "msg_...",
"status": "dry-run",
"segments": 1,
"simSlot": 1
}
If the message would split into multiple SMS segments, the response adds a warning describing the
split — the same warning a real send would give — so you can catch long-message costs before they
happen.
What still runs
Dry Run is a realistic rehearsal, not a bypass:
- Validation runs — a malformed request fails in dry run exactly as it would for real.
- The rate limiter runs — you can genuinely hit a
429while testing, which is useful for exercising your back-off code. See Security. - The live activity log records the attempt, tagged as a dry run.
What doesn’t
- No SMS is sent and no load is spent — the SIM is never touched.
- Dry runs are not written to your durable history or your today/month stats. Those numbers stay truthful, reflecting only real sends.
Next
- Failure Simulation — test your error path, not just the happy path.
- REST API — the fields these responses share with a real send.