Docs
Conformance report.
A simulator is only useful if you know exactly where it is faithful and where it is not. This page is generated from the same route table the server runs, and it states both.
Version policy
Provider APIs move; a sandbox pinned to one version becomes a liability. Where a provider puts the version in the path, Notify-Sim accepts any version and routes it to the same handler - so /v19.0/…, /v25.0/… and a version that ships next year all work without an upgrade on our side. Examples in these docs use v25.0.
| Surface | Version handling |
|---|---|
| Meta WhatsApp Cloud | Any path segment matching v<major>[.<minor>] is accepted. Requests and responses follow the Cloud API's additive shape, which has not broken across the versions we track. |
| SendGrid | v3 - the mail-send version SendGrid still ships. |
| FCM | v1 (HTTP v1 API). The legacy server-key API is not emulated. |
| Expo | v2 push API, send and getReceipts. |
| Exotel | v1 SMS send. |
| Twilio | The dated 2010-04-01 Messages resource, the only version Twilio ships. |
| MSG91 | v5 Flow API. The older v2 sendsms endpoint is not emulated. |
| Amazon SES | v2 (/v2/email/outbound-emails). The v1 Query API is not emulated. |
| Amazon SNS | The 2010-03-31 query protocol, Action=Publish only. |
| Infobip | /sms/2/text/advanced. |
| Gupshup | Enterprise WhatsApp /wa/api/v1, message and template send. |
| Kaleyra | v1 messages. |
| Route Mobile | The bulksms dialect, which is unversioned. |
| SmartPing / SPARC | The /fe/api/v1/send query-string dialect. |
Emulated surfaces
| Provider | Request | Auth | Success response |
|---|---|---|---|
| SmartPing / SPARC | GET /fe/api/v1/send | username + password in the query string | {"state":"SUBMIT_ACCEPTED", "transactionId":"…"} |
| Exotel | POST /v1/Accounts/{key}/Sms/send | HTTP Basic; the account in the path must match the key | {"SMSMessage":{"Sid":"…","Status":"queued"}} |
| Meta WhatsApp Cloud | POST /v…/{phone_id}/messages | Bearer nss_… | {"messages":[{"id":"wamid.…"}]} |
| Meta template registry | POST/GET /v…/{waba_id}/message_templates | Bearer nss_… | Template created (auto-approved) or listed |
| MSG91 | POST /api/v5/flow | authkey header | {"type":"success","message":"…"} |
| Route Mobile | GET|POST /bulksms/bulksms | username + password in the query string | 1701|<destination>|<id> (plain text) |
| Kaleyra | POST /v1/{key}/messages | api-key header; the sid must match the key | {"id":"…","status":"queued","total_count":1} |
| Twilio | POST /2010-04-01/Accounts/{key}/Messages.json | HTTP Basic; the AccountSid in the path must match the key | 201 {"sid":"SM…","status":"queued"} |
| Infobip | POST /sms/2/text/advanced | Authorization: App nss_… | {"bulkId":"…","messages":[{"status":{"name":"PENDING_ACCEPTED"}}]} |
| Gupshup | POST /wa/api/v1/msg POST /wa/api/v1/template/msg |
apikey header | {"status":"submitted","messageId":"…"} |
| Amazon SES v2 | POST /v2/email/outbound-emails | SigV4 header; the access key id in Credential= is the routing credential | {"MessageId":"…"} |
| Amazon SNS | POST /aws/sns (Action=Publish) | SigV4 header, as SES above | <PublishResponse><MessageId>… |
| SendGrid | POST /v3/mail/send | Bearer nss_… | 202, X-Message-Id header |
| Expo push | POST /--/api/v2/push/send POST /--/api/v2/push/getReceipts |
Bearer nss_… | {"data":[{"status":"ok","id":"…"}]} |
| FCM HTTP v1 | POST /v1/projects/{key}/messages:send | The unguessable project key in the path | {"name":"projects/…/messages/…"} |
| SMTP | smtp.notify-sim.io:2587 | STARTTLS, AUTH LOGIN with the project key and secret | Standard SMTP acceptance |
| Generic capture | POST /v1/capture/{sms|whatsapp|email|push} | X-Api-Key: nsk_… | The captured message as JSON |
Deterministic failure triggers
Failures are addressable rather than random, so a test that asserts a bounce is not flaky. Nothing is special-cased beyond these.
| Channel | Trigger | Result |
|---|---|---|
| SMS, WhatsApp | Recipient's digits end in 0000 | Provider-native rejection, message stored as failed |
| Recipient starts with fail@ | Simulated bounce | |
| Push | Token contains fail | DeviceNotRegistered (Expo) / UNREGISTERED 404 (FCM) |
| All | Outage realism profile | 503 from every provider surface, so retry and circuit-breaker paths run |
What is not emulated
Stated plainly, because finding out later is worse.
- Real delivery. Nothing leaves the sandbox. Delivery, read receipts and failures are modelled by the realism profile, not observed from a carrier.
- Google OAuth for FCM. Real FCM authenticates with a short-lived OAuth token minted from a service account, which we cannot mint. The unguessable project key in the path is the routing credential instead. Your payload and response shape are faithful; your token-refresh code is not exercised.
- Signature verification. Two credentials cannot be checked without a secret we never receive: FCM's OAuth token, above, and AWS SigV4. For SES and SNS the access key id inside the Credential= scope identifies the project and the signature itself is accepted unread - so set AWS_ACCESS_KEY_ID to your project key, with any secret. A wrongly signed request that real AWS would reject is accepted here.
- Template approval as a workflow. Templates you register are approved immediately. Meta's review queue, rejection reasons and quality ratings are not simulated - what is checked is that the message you send matches a template you registered.
- Carrier and gateway behaviour. No number portability, no operator-specific throughput or throttling, no real DLT scrubbing by the telco. DLT checks compare your message against the template body you registered.
- Provider endpoints beyond messaging. Contacts, campaigns, statistics, subusers, phone-number provisioning and billing endpoints are out of scope.
- Rate limits. The sandbox does not impose the provider's per-second limits, so it will not catch a burst that a real gateway would reject.
Verify this yourself
Do not take the table on trust. The local edition is the same server image this site runs, so every claim above can be checked on your own machine in a few minutes - point your app at it, send what you send in production, and compare the response to your provider's documentation.