Notify-Sim sandbox

How it works

Three steps, and the third one is the point.

Pointing an app at a sandbox is easy. What makes a sandbox worth using is what happens after the send call returns - the assertions, the failures, the delivery receipts and the replies.

Point your app at the sandbox

One base URL per channel, and your sandbox key and secret as the provider credentials. The console hands you the exact environment block for your stack - SMTP, SmartPing, Meta, SendGrid, Expo, FCM.

Send exactly as you do in production

Your code, your payloads, your templates. Notify-Sim answers like the real gateway, captures the message, and streams it into the inbox as it happens.

Assert it in your tests

Wait for the message your flow should have produced, read the OTP out of the body, and check what the recipient would have seen - in pytest, in CI, on every commit.

tests/test_signup.py
from notifysim import Sandbox

sb = Sandbox(base_url="https://api.notify-sim.io",
             api_key="nsk_…")

def test_signup_sends_otp(client):
    sb.clear()
    client.post("/signup", json={"phone": "+919848012345"})

    msg = sb.sms.expect(to="+919848012345", timeout=10)
    assert msg.template_match == "match"   # DLT verbatim
    otp = msg.extract(r"\b(\d{6})\b")

    client.post("/verify", json={"otp": otp})

What the assertion API gives you

  • expect() blocks until the message arrives, so tests are not sleep-and-hope.
  • Every field the provider saw: body, subject, HTML, attachments, template name and parameters, DLT id.
  • Failure on demand from the recipient alone, so a retry test is repeatable.
  • A clean slate per run: clear the project, or give CI its own sandbox.
  • A live feed over server-sent events, the same one the console uses.

Python SDK today. Node SDK next; the HTTP API is stable and documented.

The full loop

Sending is a quarter of the integration.

Most of what breaks in notifications happens after the send call returns. Notify-Sim keeps going: lifecycle, delivery receipts signed the way the provider signs them, and replies coming back the other way.

1Captured

The provider response your code expects, immediately - success body, message id, or the gateway's own rejection.

accepted

2Lifecycle

Messages move through submitted, delivered and read on a schedule you pick: instant, realistic India, degraded, or a total outage.

submitted → delivered → read

3Delivery receipts

Each status is POSTed to your webhook in that provider's native shape and signature, with retries, a delivery log and one-click replay.

X-Hub-Signature-256

4Replies inbound

Answer as the customer from the inbox; your app receives a real inbound webhook, so two-way flows are exercised, not imagined.

messages[] · SMS MO

Failure lab

Break it on purpose, the same way every time.

Failures are addressed, not random. Each one returns the provider's real error, so your handler is tested against what it will actually receive.

Send toChannelYou get back
any number ending 0000SMS, WhatsAppSmartPing REJECTED / Meta 131026
fail@anything.comEmailSMTP 550 at RCPT, or a SendGrid bounce
a token containing "fail"PushExpo DeviceNotRegistered / FCM UNREGISTERED
outage profileAllThe provider's own 503 at ingest - nothing is captured

Realism profiles

Operator-grade latency, quiet hours and soft failures on demand. Flip a project to outage and every ingest answers with the provider's own 503 - the honest way to test a fallback path.

Webhook console

Every attempt with request body, response code, response body and attempt count. When your handler 500s, you can see exactly what it was handed - and replay it.

Projects per environment

Separate credentials and inboxes for local, CI and staging, so a noisy test run never pollutes the inbox someone is reading.

Console

Everything the team needs around the inbox.

Live inbox

Messages stream in as they are captured, filterable by channel, recipient, content or failure, with the provider payload one tab away.

Your team, with roles

Invite by email. Admins manage members and billing, developers drive the sandbox, viewers read the inbox - useful for whoever approves the copy.

Credentials you can rotate

Sandbox secrets and API keys are stored hashed and shown once. Rotate whenever a laptop or a CI runner changes hands.

Retention that suits testing

Captured messages age out on your plan's schedule. Nothing needs cleaning up by hand, and test data does not accumulate forever.

Free on your machine

The local edition is one Docker container with the same inbox and the same provider emulation, free forever, no account.

Nothing can escape

There is no route out to a carrier, to Meta or to a mailbox. That is what makes it safe to point staging at production-shaped data.

Start in about two minutes

Send one message. Watch it arrive as a phone screen.

Create a project, copy the environment block for your stack, and run the flow you were about to test anyway. The free tier does not expire and needs no card.