Skip to main content
An Edge Compute function is a real container running your own HTTP server — not a per-request sandbox. Most of the practices below follow from that model: module scope runs once per container, an escaped exception kills a process, and state has to live somewhere other than the container. Code samples are TypeScript. The same principles apply in every runtime; the binding SDK (@telnyx/edge-runtime) is TypeScript-only today, so other languages use environment variables and the REST APIs where a binding is shown.

Configuration

Keep Secrets Out of Code

Store credentials as secrets — the CLI takes the key and value as positional arguments:
Every secret is injected into your functions as a plain environment variable, so this works in any language:
TypeScript projects that declare a [[secrets]] binding in func.toml can also read it through env.SECRETS.get("<handle>"), which telnyx-edge types type-checks against the declared handles. Both surfaces are live at the same time — see Secrets.

Budget for the Platform Timeout

The request timeout is 30 seconds by default and 60 seconds at most — there is no func.toml field that raises it. A request that exceeds it is terminated with a 504. Set your own deadlines on outbound calls a few seconds below the platform’s so you fail with a useful error instead (see Time Out and Retry Outbound Calls), and split work that genuinely needs longer. Exact numbers: Limits.

Name Functions for Their URL

The function name becomes the hostname — {func-name}-{org-nickname}.telnyxcompute.com — and new-func registers the function with the platform at scaffold time, so pick the name up front:

Performance

Initialize Once, at Module Scope

A container serves many requests. Module scope runs once per container; the request callback runs per request. Build clients, load config, and compile anything expensive outside the callback:
The same rule holds everywhere: package-level variables in Go, module-level objects in Python.

Keep Cold Starts Small

A new container starts when traffic scales up or after a deploy, and the first request it serves waits for everything before server.listen — imports, client construction, config loads. Keep dependencies minimal, and lazy-load heavy libraries used only on rare paths so the common path doesn’t pay for them.

Cache Expensive Reads in KV

Declare a KV namespace in func.toml and it resolves as a binding on env:
Two contracts to know:
  • expirationTtl is server-side expiry in whole seconds (≥ 1) and requires @telnyx/edge-runtime ≥ 0.2.2 — earlier versions accept the option and silently ignore it.
  • Keys allow a-z A-Z 0-9 - _ / = . and forbid colons — write user/123, not user:123.
More in KV Best Practices.

Put State Where It Belongs

Containers come and go, and concurrent requests can land on different containers — anything kept in process memory is a cache at best. Pick the store by the shape of the data: Don’t build counters, locks, or rate limiters on KV: it has no transactions or compare-and-swap, and concurrent writers to one key are last-write-wins. That job is exactly what Stateful Actors exist for. Full comparison: Where state lives.

Reliability

Make Handlers Idempotent

Clients retry and webhooks are redelivered, so design handlers where processing the same request twice has the same effect as once. Key side effects on a caller-supplied identifier — a webhook event id, an Idempotency-Key header — and skip work already done. If the duplicate check itself must be race-free, do it inside a Stateful Actor; a check-then-act on KV can race.

Keep the Health Endpoint Fast

The scaffolds answer /health before any other routing:
Keep that property: return immediately and never call a dependency from it, so a slow upstream can’t make your function look down.

Catch Everything at the Top of the Handler

Your function is one process. An exception that escapes the request callback — including an unhandled promise rejection — crashes it, drops every in-flight request, and makes the next request pay a cold start. Wrap the whole handler body:

Time Out and Retry Outbound Calls

Don’t let a slow upstream ride you into the platform’s 30-second 504 — set an explicit deadline on every outbound call:
Retry only network failures and 5xx responses, with exponential backoff, and keep the total budget under the platform timeout:

Security

  • Validate input before use — check required fields and types, reject with 400. Nothing between the internet and your handler does it for you.
  • HTTPS only for outbound calls.
  • Never log secret values — and don’t log full request bodies, which may carry PII. Log metadata: method, path, status, duration.
  • Authenticate anything that mutates state — your function URL is public. Require a token or shared secret (stored as a secret, checked in the handler) before acting on a request.

Observability

There is no logs command and no metrics dashboard today. What you have is the output of ship, status, and inspect — plus whatever your function emits itself. So emit deliberately:
  • Send structured events over HTTP to a sink you control (a log aggregator, your own collector) if you need request-level visibility. There is no surface that shows you console.log output.
  • Propagate a request id — read X-Request-ID or generate one, return it in the response, and attach it to every event you emit, so a user-reported failure is findable in your sink.
Patterns and sink examples: Observability.

Next Steps

  • Limits — the exact numbers behind timeouts, memory, and payload sizes
  • Observability — building your own telemetry with no platform logging surface
  • Execution Model — container lifecycle, scaling, and cold starts