Skip to content
Last updated

Local vs production

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.

Two packages per runtime, one import path

Each runtime publishes two flavors of its package:

Dev packageProd package
What you install@sinch/functions-runtime (Node) / Sinch.Functions.Runtime (NuGet)Same name
What you import@sinch/functions-runtimeSame name
CacheIn-memory, lost on restartPersistent, shared across invocations
StorageLocal filesystem (./storage/)Durable cloud storage with local read cache
DatabasePlain SQLite file in ./data/SQLite with automatic durable replication
SecretsOS keychain + .env filePlatform secret store, injected as env vars
TunnelLocal tunnel via sinch functions devNot present — the function has a real URL
LoggingConsole outputCaptured, 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.

What this means for your code

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() or NODE_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().

What local dev gives you that production doesn't

Hot reload

  • Node: tsup watches your source and rebuilds on save.
  • C#: dotnet watch rebuilds and restarts.

Your function reloads in under a second — no deploy loop to iterate.

Public tunnel

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.

Interactive logs TUI

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.

What production gives you that local dev doesn't

  • 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.

The "feature parity" contract

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.