The Sinch runtime is designed so that the code you write runs the same in both environments. No "if (dev) use this library, else use that one" branches scattered through your function. The differences are swapped in at build time, not in your code.
Each runtime publishes two flavors of its package:
| Dev package | Prod package | |
|---|---|---|
| What you install | @sinch/functions-runtime (Node) / Sinch.Functions.Runtime (NuGet) | Same name |
| What you import | @sinch/functions-runtime | Same name |
| Cache | In-memory, lost on restart | Persistent, shared across invocations |
| Storage | Local filesystem (./storage/) | Durable cloud storage with local read cache |
| Database | Plain SQLite file in ./data/ | SQLite with automatic durable replication |
| Secrets | OS keychain + .env file | Platform secret store, injected as env vars |
| Tunnel | Local tunnel via sinch functions dev | Not present — the function has a real URL |
| Logging | Console output | Captured, indexed, streamed via sinch functions logs |
You never reference the prod package directly. The build pipeline swaps it in during sinch functions deploy, so your imports stay the same and your code is identical.
You can write:
await context.cache.set('session:abc', data, 1800);And it works locally (in-memory) and in production (distributed cache) with zero changes. Same for storage, database, and SDK clients.
The few places where you do need to care about the environment:
- Log verbosity. You may want more detail in dev —
createConfig(context).isDevelopment()orNODE_ENV === 'development'gives you the switch. - External integrations with dev vs prod accounts. If you use Stripe test vs live keys, or two different Voice Apps, your own configuration decides which to use — set different secrets per environment via
sinch secrets add. - Behavior that should never run locally. For example, "send a real SMS to the customer" might be skipped when
isDevelopment().
- Node:
tsupwatches your source and rebuilds on save. - C#:
dotnet watchrebuilds and restarts.
Your function reloads in under a second — no deploy loop to iterate.
sinch functions dev can open a public tunnel that points at your local process. This is how Sinch reaches your laptop. When you accept the tunnel prompt, the Sinch CLI optionally updates your Voice App's callback URL to the tunnel URL so real calls hit your laptop directly.
When the dev server exits, the tunnel closes and the CLI reverts any callback URLs it touched.
sinch functions logs --follow opens a terminal UI that shows each incoming request with full payload, headers, and response. Arrow keys to navigate, Enter to expand, q to quit. Much faster than grepping a log file.
- A stable URL.
https://fn-<id>.functions.sinch.com— doesn't change between deploys. - Auto-scaling. The platform spins more instances up under load.
- Persistent cache and storage. Nothing disappears when the process restarts.
- Platform middleware. Billing, logging, webhook signature validation, and error reporting all run automatically.
- Durable database replication. Your SQLite database is continuously backed up, so it survives restarts and scale events.
Every API on the FunctionContext — cache, storage, database, SDK clients, logger — behaves the same in both environments. If it works locally, it works deployed. If it breaks in one but not the other, that is a runtime bug, not something you should work around in your code.
The one practical caveat: dev storage is on your laptop's disk, so you will not hit production-scale limits locally. Test with realistic payload sizes before shipping anything that writes a lot of data.
- Configuration & secrets — how variables and secrets flow into the runtime
- Deployment — what actually happens when you run
sinch functions deploy - Context object — the APIs that are consistent across dev and prod