# Telnyx Edge: Compute — Full Documentation > Complete page content for Compute (Edge section) of the Telnyx developer docs (https://developers.telnyx.com). > Root index: https://developers.telnyx.com/llms.txt · Lightweight index for this subsection: https://telnyx-openapi-ng.s3.us-east-1.amazonaws.com/llms/edge/compute.txt ## Overview ### Edge Compute > Source: https://developers.telnyx.com/docs/edge-compute/overview.md A serverless platform for deploying code across Telnyx's carrier-grade infrastructure — low latency, global distribution, no infrastructure to manage. With Telnyx Edge Compute, you can: - **Deploy globally** — Functions run on edge nodes closest to your users - **Build in any language** — JavaScript, TypeScript, Python, Go, and Java (Quarkus) - **Integrate with Telnyx** — Direct access to Voice, Messaging, and AI APIs - **Scale automatically** — No provisioning, no cold start tuning - **Pay for what you use** — Free tier included, simple usage-based pricing Deploy your first function in 5 minutes Copy-paste code snippets for common patterns --- ## Build with Edge Compute Build REST APIs, webhooks, and microservices with automatic scaling Process voice, messaging, and AI events with minimal latency Transform, validate, and route data at the edge before storage Run background tasks on a schedule with cron triggers --- ## Integrate with Edge Compute Connect to storage and services via bindings — configured in `func.toml`, available at runtime: **Storage** Low-latency key-value storage for caching and session data Serverless SQL database with SQLite compatibility S3-compatible storage for files and media **Telnyx Services** Handle calls, transcribe audio, build IVRs Send and receive SMS/MMS with custom logic Transcription, inference, and embeddings at the edge --- ## Pricing Edge Compute is billed based on requests and CPU time. Free tier included. | Metric | Free Allowance | Rate | |--------|----------------|------| | Requests | 3.6M / month | $0.21 / million | | CPU Time | 36M ms / month | $0.014 / million ms | --- ## Resources Complete CLI command reference How Edge Compute works under the hood Request limits, timeouts, and quotas Get help from the community --- ## Getting Started ### Quick Start > Source: https://developers.telnyx.com/docs/edge-compute/quickstart.md ## Tutorial Objectives This tutorial covers learning the basic Edge Compute development workflow. ## Prerequisites - **A Telnyx account with API access** - [Sign up here](https://telnyx.com/sign-up) if needed. - **Command line familiarity** - Requires Terminal (macOS/Linux) or Command Prompt (Windows). - **Time requirement** - This guide takes approximately 5 minutes to complete. ## Install the CLI ### Download Binary Check out the [edge-compute repo](https://github.com/team-telnyx/edge-compute) and download the latest release from the [releases page](https://github.com/team-telnyx/edge-compute/releases). Extract the archive and add the binary to your PATH: Example for Linux/macOS: ```bash # Download and extract tar -xzf telnyx-edge-*.tar.gz # Install to system PATH sudo mv telnyx-edge /usr/local/bin/ # Verify installation telnyx-edge --help ``` ## Authenticate ### Option 1: OAuth (Recommended) Authenticate using OAuth with your Telnyx account: ```bash telnyx-edge auth login ``` This will open your browser to complete authentication. ### Option 2: API Key Alternatively, authenticate with a Telnyx API key: ```bash telnyx-edge auth api-key set YOUR_API_KEY ``` ### Verify Authentication Check your authentication status: ```bash telnyx-edge auth status ``` ## Create a Function Create a simple "Hello World" function. Choose your preferred language: ```bash telnyx-edge new-func --language=go --name=hello-world ``` This creates a new directory with the function: ``` hello-world/ ├── func.toml # Function configuration ├── go.mod # Go dependencies └── handler.go # Your function code ``` ### Function Code The generated Go function uses `package function` with a `Handle` function: ```go package function import ( "encoding/json" "io" "log" "net/http" ) type Response struct { Message string `json:"message"` Status string `json:"status"` } func Handle(w http.ResponseWriter, r *http.Request) { log.Printf("Serving request: %s %s", r.Method, r.URL.Path) response := Response{ Message: "Hello from Telnyx Edge Compute!", Status: "success", } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(response) } ``` ```bash telnyx-edge new-func --language=js --name=hello-world ``` This creates a new directory with the function: ``` hello-world/ ├── func.toml # Function configuration ├── package.json # Node.js dependencies └── index.js # Your function code ``` ### Function Code The generated JavaScript function uses a raw HTTP server: ```javascript const http = require('http'); const server = http.createServer((req, res) => { // Health endpoints for Knative probes if (req.url === '/health/liveness' || req.url === '/health/readiness') { res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end('ok'); } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ message: 'Hello from Telnyx Edge Compute!', status: 'success', })); }); server.listen(8080); ``` ```bash telnyx-edge new-func --language=ts --name=hello-world ``` This creates a new directory with the function: ``` hello-world/ ├── func.toml # Function configuration ├── package.json # Node.js dependencies ├── tsconfig.json # TypeScript configuration └── index.ts # Your function code ``` ### Function Code The generated TypeScript function uses a raw HTTP server: ```typescript import * as http from 'http'; const server = http.createServer((req: http.IncomingMessage, res: http.ServerResponse) => { // Health endpoints for Knative probes if (req.url === '/health/liveness' || req.url === '/health/readiness') { res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end('ok'); } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ message: 'Hello from Telnyx Edge Compute!', status: 'success', })); }); server.listen(8080); ``` ```bash telnyx-edge new-func --language=python --name=hello-world ``` This creates a new directory with the function: ``` hello-world/ ├── function/ # Your function code │ └── __init__.py ├── pyproject.toml # Python dependencies └── func.toml # Function configuration ``` ### Function Code The generated Python function uses ASGI protocol with a factory pattern: ```python # function/func.py import json def new(): """Factory that returns a Function instance""" return Function() class Function: async def handle(self, scope, receive, send): # Parse the request method = scope.get("method", "GET") path = scope.get("path", "/") # Build response response = { "message": "Hello from Telnyx Edge Compute!", "status": "success" } # Send response using ASGI protocol await send({ "type": "http.response.start", "status": 200, "headers": [ [b"content-type", b"application/json"], ], }) await send({ "type": "http.response.body", "body": json.dumps(response).encode(), }) ``` The entry point is `new()` which returns a `Function` instance. ```bash telnyx-edge new-func --language=quarkus --name=hello-world ``` This creates a new directory with the function: ``` hello-world/ ├── src/main/java/ # Your function code ├── pom.xml # Maven dependencies └── func.toml # Function configuration ``` ### Function Code The generated Java function uses Quarkus Funqy with Input/Output beans: ```java package functions; import io.quarkus.funqy.Funq; public class Function { @Funq public Output function(Input input) { String defaultMessage = "Hello from Telnyx Edge Compute!"; if (input == null || input.getMessage() == null) { return new Output(defaultMessage); } return new Output(input.getMessage()); } } ``` ## Deploy the Function Navigate to the function directory and deploy: ```bash cd hello-world telnyx-edge ship ``` The CLI will: 1. Validate your function structure. 2. Check authentication. 3. Package your function files. 4. Upload to Telnyx Edge infrastructure. 5. Deploy across edge locations. You'll see output like: ``` Deploying function 'hello-world'... ✓ Function validated successfully ✓ Authentication verified ✓ Files packaged (2.3 KB) ✓ Upload complete ✓ Function deployed successfully Function ID: func-abc123def456 Status: Active ``` ## Test the Function Your function is now deployed and accessible via HTTP. Functions are accessible at URLs following this pattern: `https://{funcName}-{orgId}.telnyxcompute.com` Test your deployed function: ```bash curl https://hello-world-abc123.telnyxcompute.com \ -H "Content-Type: application/json" \ -d '{"test": "data"}' ``` Response: ```json { "message": "Hello from Telnyx Edge Compute!", "status": "success" } ``` ## Success! The Function is Live Your function is now running and ready to handle HTTP requests. Functions automatically scale based on demand. ## Function Anatomy Every Telnyx Edge Compute function consists of three essential components: ### Function Code The main application code that handles HTTP requests and responses: ```go // Go example package function import ( "encoding/json" "net/http" ) func Handle(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(map[string]string{ "message": "Hello from the edge", }) } ``` ```javascript // JavaScript example const http = require('http'); http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ message: 'Hello from the edge' })); }).listen(8080); ``` ```typescript // TypeScript example import * as http from 'http'; http.createServer((req: http.IncomingMessage, res: http.ServerResponse) => { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ message: 'Hello from the edge' })); }).listen(8080); ``` ```python # Python example class Function: async def handle(self, scope, receive, send): await send({ "type": "http.response.start", "status": 200, "headers": [[b"content-type", b"application/json"]], }) await send({ "type": "http.response.body", "body": b'{"message": "Hello from the edge"}', }) ``` ```java // Java (Quarkus Funqy) example package functions; import io.quarkus.funqy.Funq; public class Function { @Funq public Output function(Input input) { return new Output("Hello from the edge"); } } ``` ### Runtime Dependencies Language-specific dependency files that define required packages: ```mod // go.mod (Go) module my-edge-function go 1.21 ``` ```json // package.json (JavaScript/TypeScript) { "name": "my-edge-function", "version": "1.0.0", "main": "index.js" } ``` ```toml # pyproject.toml (Python) [project] name = "my-edge-function" version = "0.1.0" dependencies = [] ``` ```xml io.quarkus quarkus-funqy ``` ### Function Configuration The `func.toml` file that defines deployment and runtime configuration: ```toml [edge_compute] func_id = "func-abc123def456" func_name = "hello-world" [env_vars] MY_KEY = "VAL" ``` ## Next Steps Now that your function is deployed, explore these topics to go deeper: - **[Runtime APIs](/docs/edge-compute/runtime)** - Request/response patterns and the platform interface. - **[Execution Model](/docs/edge-compute/runtime/execution-model)** - Function lifecycle, cold starts, and container behavior. - **[Configuration](/docs/edge-compute/configuration)** - Environment variables, secrets, and bindings. - **[Local Development](/docs/edge-compute/development)** - Build, test, and debug functions locally. - **[CLI Reference](/docs/edge-compute/reference/cli)** - All available CLI commands. --- ## Examples ### View Examples > Source: https://developers.telnyx.com/docs/edge-compute/examples.md Bite-sized code snippets for common edge function patterns. Each example is self-contained — copy, paste, deploy. ## Return JSON ```javascript const http = require('http'); http.createServer((req, res) => { if (req.url === '/health/liveness' || req.url === '/health/readiness') { res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end('ok'); } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ success: true, data: { id: 123, name: "John" } })); }); server.listen(8080); ``` ```typescript import http from 'http'; const server = http.createServer((req: http.IncomingMessage, res: http.ServerResponse) => { if (req.url === '/health/liveness' || req.url === '/health/readiness') { res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end('ok'); } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ success: true, data: { id: 123, name: "John" } })); }); server.listen(8080); ``` ```go func handler(w http.ResponseWriter, r *http.Request) { response := map[string]interface{}{ "success": true, "data": map[string]interface{}{ "id": 123, "name": "John", }, } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(response) } ``` ```python def do_GET(self): response = { "success": True, "data": {"id": 123, "name": "John"} } self.send_response(200) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(json.dumps(response).encode()) ``` ```java @GET @Produces(MediaType.APPLICATION_JSON) public Response getData() { return Response.ok(Map.of( "success", true, "data", Map.of("id", 123, "name", "John") )).build(); } ``` ## Return HTML ```javascript const http = require('http'); http.createServer((req, res) => { if (req.url === '/health/liveness' || req.url === '/health/readiness') { res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end('ok'); } const html = ` Hello from Edge `; res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(html); }); server.listen(8080); ``` ```typescript import http from 'http'; const server = http.createServer((req: http.IncomingMessage, res: http.ServerResponse) => { if (req.url === '/health/liveness' || req.url === '/health/readiness') { res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end('ok'); } const html: string = ` Hello from Edge `; res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(html); }); server.listen(8080); ``` ```go func handler(w http.ResponseWriter, r *http.Request) { html := `Hello from Edge` w.Header().Set("Content-Type", "text/html") w.WriteHeader(http.StatusOK) fmt.Fprint(w, html) } ``` ```python def do_GET(self): html = "Hello from Edge" self.send_response(200) self.send_header("Content-Type", "text/html") self.end_headers() self.wfile.write(html.encode()) ``` ```java @GET @Produces(MediaType.TEXT_HTML) public Response getHtml() { String html = "Hello from Edge"; return Response.ok(html).build(); } ``` ## Return Binary ```javascript const http = require('http'); http.createServer(async (req, res) => { if (req.url === '/health/liveness' || req.url === '/health/readiness') { res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end('ok'); } const imageResponse = await fetch("https://your-bucket.s3.amazonaws.com/logo.png"); const imageData = Buffer.from(await imageResponse.arrayBuffer()); res.writeHead(200, { 'Content-Type': 'image/png' }); res.end(imageData); }); server.listen(8080); ``` ```typescript import http from 'http'; const server = http.createServer(async (req: http.IncomingMessage, res: http.ServerResponse) => { if (req.url === '/health/liveness' || req.url === '/health/readiness') { res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end('ok'); } const imageResponse: Response = await fetch("https://your-bucket.s3.amazonaws.com/logo.png"); const imageData: Buffer = Buffer.from(await imageResponse.arrayBuffer()); res.writeHead(200, { 'Content-Type': 'image/png' }); res.end(imageData); }); server.listen(8080); ``` ```go func handler(w http.ResponseWriter, r *http.Request) { imageData, err := os.ReadFile("logo.png") if err != nil { http.Error(w, "Image not found", http.StatusNotFound) return } w.Header().Set("Content-Type", "image/png") w.Write(imageData) } ``` ```python def do_GET(self): with open("logo.png", "rb") as f: image_data = f.read() self.send_response(200) self.send_header("Content-Type", "image/png") self.end_headers() self.wfile.write(image_data) ``` ```java @GET @Produces("image/png") public Response getImage() throws IOException { byte[] imageData = Files.readAllBytes(Path.of("logo.png")); return Response.ok(imageData).build(); } ``` ## Parse Query Parameters ```javascript const http = require('http'); const { URL } = require('url'); http.createServer((req, res) => { if (req.url === '/health/liveness' || req.url === '/health/readiness') { res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end('ok'); } const url = new URL(req.url, `http://${req.headers.host}`); const name = url.searchParams.get('name') || 'World'; res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ message: `Hello ${name} from Telnyx Edge Compute!`, status: "success" })); }); server.listen(8080); ``` ```typescript import http from 'http'; import { URL } from 'url'; const server = http.createServer((req: http.IncomingMessage, res: http.ServerResponse) => { if (req.url === '/health/liveness' || req.url === '/health/readiness') { res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end('ok'); } const parsedUrl: URL = new URL(req.url || '', `http://${req.headers.host}`); const name: string = parsedUrl.searchParams.get('name') || 'World'; res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ message: `Hello ${name} from Telnyx Edge Compute!`, status: "success" })); }); server.listen(8080); ``` ```go func handler(w http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get("name") if name == "" { name = "World" } response := Response{ Message: fmt.Sprintf("Hello %s from Telnyx Edge Compute!", name), Status: "success", } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(response) } ``` ```python from urllib.parse import urlparse, parse_qs def do_GET(self): query = parse_qs(urlparse(self.path).query) name = query.get("name", ["World"])[0] response = { "message": f"Hello {name} from Telnyx Edge Compute!", "status": "success" } self.send_response(200) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(json.dumps(response).encode()) ``` ```java import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.DefaultValue; @GET @Produces(MediaType.APPLICATION_JSON) public Response hello(@QueryParam("name") @DefaultValue("World") String name) { return Response.ok(Map.of( "message", "Hello " + name + " from Telnyx Edge Compute!", "status", "success" )).build(); } ``` ## Handle HTTP Methods ```javascript const http = require('http'); http.createServer((req, res) => { if (req.url === '/health/liveness' || req.url === '/health/readiness') { res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end('ok'); } let message; let status = 200; switch (req.method) { case 'GET': message = 'Hello from GET request!'; break; case 'POST': message = 'Data received via POST!'; break; default: message = 'Method not supported'; status = 405; } res.writeHead(status, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ message, status: status === 200 ? 'success' : 'error' })); }); server.listen(8080); ``` ```typescript import http from 'http'; const server = http.createServer((req: http.IncomingMessage, res: http.ServerResponse) => { if (req.url === '/health/liveness' || req.url === '/health/readiness') { res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end('ok'); } let message: string; let status: number = 200; switch (req.method) { case 'GET': message = 'Hello from GET request!'; break; case 'POST': message = 'Data received via POST!'; break; default: message = 'Method not supported'; status = 405; } res.writeHead(status, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ message, status: status === 200 ? 'success' : 'error' })); }); server.listen(8080); ``` ```go func handler(w http.ResponseWriter, r *http.Request) { var response Response switch r.Method { case "GET": response = Response{Message: "Hello from GET request!", Status: "success"} case "POST": response = Response{Message: "Data received via POST!", Status: "success"} default: response = Response{Message: "Method not supported", Status: "error"} w.WriteHeader(http.StatusMethodNotAllowed) } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(response) } ``` ```python def do_GET(self): response = {"message": "Hello from GET request!", "status": "success"} self.send_response(200) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(json.dumps(response).encode()) def do_POST(self): response = {"message": "Data received via POST!", "status": "success"} self.send_response(200) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(json.dumps(response).encode()) ``` ```java @GET @Produces(MediaType.APPLICATION_JSON) public Response handleGet() { return Response.ok(Map.of( "message", "Hello from GET request!", "status", "success" )).build(); } @POST @Produces(MediaType.APPLICATION_JSON) public Response handlePost(Map body) { return Response.ok(Map.of( "message", "Data received via POST!", "status", "success" )).build(); } ``` ## Read Request Body ```javascript const http = require('http'); http.createServer((req, res) => { if (req.url === '/health/liveness' || req.url === '/health/readiness') { res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end('ok'); } if (req.method !== 'POST') { res.writeHead(405, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ error: 'Method not allowed' })); } let body = ''; req.on('data', chunk => { body += chunk; }); req.on('end', () => { try { const data = JSON.parse(body); if (!data.email) { res.writeHead(400, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ error: 'Email is required' })); } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ status: 'success', email: data.email })); } catch { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Invalid JSON' })); } }); }); server.listen(8080); ``` ```typescript import http from 'http'; interface RequestData { email?: string; } const server = http.createServer((req: http.IncomingMessage, res: http.ServerResponse) => { if (req.url === '/health/liveness' || req.url === '/health/readiness') { res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end('ok'); } if (req.method !== 'POST') { res.writeHead(405, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ error: 'Method not allowed' })); } let body: string = ''; req.on('data', (chunk: Buffer) => { body += chunk; }); req.on('end', () => { try { const data: RequestData = JSON.parse(body); if (!data.email) { res.writeHead(400, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ error: 'Email is required' })); } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ status: 'success', email: data.email })); } catch { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Invalid JSON' })); } }); }); server.listen(8080); ``` ```go func handler(w http.ResponseWriter, r *http.Request) { var data struct { Email string `json:"email"` } if err := json.NewDecoder(r.Body).Decode(&data); err != nil { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(map[string]string{"error": "Invalid JSON"}) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"status": "success", "email": data.Email}) } ``` ```python def do_POST(self): try: content_length = int(self.headers.get('Content-Length', 0)) body = self.rfile.read(content_length) data = json.loads(body) self.send_response(200) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(json.dumps({"status": "success", "received": data}).encode()) except json.JSONDecodeError: self.send_response(400) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(json.dumps({"error": "Invalid JSON"}).encode()) ``` ```java @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response handleRequest(Map data) { if (data == null || !data.containsKey("email")) { return Response.status(400) .entity(Map.of("error", "Email is required")) .build(); } return Response.ok(Map.of("status", "success")).build(); } ``` ## Use Environment Variables ```javascript const http = require('http'); http.createServer((req, res) => { if (req.url === '/health/liveness' || req.url === '/health/readiness') { res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end('ok'); } const greeting = process.env.GREETING || 'Hello'; res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ message: `${greeting} from Telnyx Edge Compute!`, status: 'success' })); }); server.listen(8080); ``` ```typescript import http from 'http'; const server = http.createServer((req: http.IncomingMessage, res: http.ServerResponse) => { if (req.url === '/health/liveness' || req.url === '/health/readiness') { res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end('ok'); } const greeting: string = process.env.GREETING || 'Hello'; res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ message: `${greeting} from Telnyx Edge Compute!`, status: 'success' })); }); server.listen(8080); ``` ```go func handler(w http.ResponseWriter, r *http.Request) { greeting := os.Getenv("GREETING") if greeting == "" { greeting = "Hello" } response := Response{ Message: fmt.Sprintf("%s from Telnyx Edge Compute!", greeting), Status: "success", } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(response) } ``` ```python import os def do_GET(self): greeting = os.environ.get("GREETING", "Hello") response = { "message": f"{greeting} from Telnyx Edge Compute!", "status": "success" } self.send_response(200) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(json.dumps(response).encode()) ``` ```java import org.eclipse.microprofile.config.inject.ConfigProperty; @Path("/") public class HelloResource { @ConfigProperty(name = "GREETING", defaultValue = "Hello") String greeting; @GET @Produces(MediaType.APPLICATION_JSON) public Response hello() { return Response.ok(Map.of( "message", greeting + " from Telnyx Edge Compute!", "status", "success" )).build(); } } ``` ## Access Secrets Secrets are injected as environment variables but stored encrypted. Use them for API keys, tokens, and credentials. ```javascript const http = require('http'); http.createServer(async (req, res) => { if (req.url === '/health/liveness' || req.url === '/health/readiness') { res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end('ok'); } const apiKey = process.env.EXTERNAL_API_KEY; if (!apiKey) { res.writeHead(500, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ error: 'API key not configured' })); } const response = await fetch('https://api.example.com/data', { headers: { 'Authorization': `Bearer ${apiKey}` } }); const data: unknown = await response.json(); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(data)); }); server.listen(8080); ``` ```typescript import http from 'http'; const server = http.createServer(async (req: http.IncomingMessage, res: http.ServerResponse) => { if (req.url === '/health/liveness' || req.url === '/health/readiness') { res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end('ok'); } const apiKey: string | undefined = process.env.EXTERNAL_API_KEY; if (!apiKey) { res.writeHead(500, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ error: 'API key not configured' })); } const response: Response = await fetch('https://api.example.com/data', { headers: { 'Authorization': `Bearer ${apiKey}` } }); const data: unknown = await response.json(); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(data)); }); server.listen(8080); ``` ```go func handler(w http.ResponseWriter, r *http.Request) { apiKey := os.Getenv("EXTERNAL_API_KEY") if apiKey == "" { http.Error(w, "API key not configured", http.StatusInternalServerError) return } req, _ := http.NewRequest("GET", "https://api.example.com/data", nil) req.Header.Set("Authorization", "Bearer "+apiKey) resp, err := http.DefaultClient.Do(req) if err != nil { http.Error(w, "External API error", http.StatusBadGateway) return } defer resp.Body.Close() w.Header().Set("Content-Type", "application/json") io.Copy(w, resp.Body) } ``` ```python import os import httpx def do_GET(self): api_key = os.environ.get("EXTERNAL_API_KEY") if not api_key: self.send_error(500, "API key not configured") return response = httpx.get( "https://api.example.com/data", headers={"Authorization": f"Bearer {api_key}"} ) self.send_response(200) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(response.content) ``` ```java @ApplicationScoped public class SecureResource { @ConfigProperty(name = "EXTERNAL_API_KEY") String apiKey; @Inject @RestClient ExternalApiClient client; @GET @Produces(MediaType.APPLICATION_JSON) public Response fetchData() { var data = client.getData(apiKey); return Response.ok(data).build(); } } ``` ## KV Read/Write **Coming Soon** — KV storage is in development. The examples below show the expected API pattern. Access [KV storage](/docs/edge-compute/kv) for low-latency key-value data. ```javascript const http = require('http'); const KV_ID = process.env.KV_MY_STORE_ID; const API_KEY = process.env.TELNYX_API_KEY; const BASE_URL = `https://api.telnyx.com/v2/storage/kvs/${KV_ID}`; async function kvGet(key) { const response = await fetch(`${BASE_URL}/keys/${key}`, { headers: { 'Authorization': `Bearer ${API_KEY}` } }); if (response.status === 404) return null; const data = await response.json(); return data.value; } async function kvPut(key, value, ttl) { await fetch(`${BASE_URL}/keys/${key}`, { method: 'PUT', headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ value, ttl }) }); } http.createServer(async (req, res) => { if (req.url === '/health/liveness' || req.url === '/health/readiness') { res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end('ok'); } const cachedValue = await kvGet('user:123'); if (cachedValue) { res.writeHead(200, { 'Content-Type': 'application/json' }); return res.end(cachedValue); } const userData = JSON.stringify({ id: 123, name: 'Alice' }); await kvPut('user:123', userData, 3600); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(userData); }); server.listen(8080); ``` ```typescript import http from 'http'; const KV_ID: string | undefined = process.env.KV_MY_STORE_ID; const API_KEY: string | undefined = process.env.TELNYX_API_KEY; const BASE_URL: string = `https://api.telnyx.com/v2/storage/kvs/${KV_ID}`; async function kvGet(key: string): Promise { const response: Response = await fetch(`${BASE_URL}/keys/${key}`, { headers: { 'Authorization': `Bearer ${API_KEY}` } }); if (response.status === 404) return null; const data: { value: string } = await response.json(); return data.value; } async function kvPut(key: string, value: string, ttl: number): Promise { await fetch(`${BASE_URL}/keys/${key}`, { method: 'PUT', headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ value, ttl }) }); } const server = http.createServer(async (req: http.IncomingMessage, res: http.ServerResponse) => { if (req.url === '/health/liveness' || req.url === '/health/readiness') { res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end('ok'); } const cachedValue: string | null = await kvGet('user:123'); if (cachedValue) { res.writeHead(200, { 'Content-Type': 'application/json' }); return res.end(cachedValue); } const userData: string = JSON.stringify({ id: 123, name: 'Alice' }); await kvPut('user:123', userData, 3600); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(userData); }); server.listen(8080); ``` ```go var ( kvID = os.Getenv("KV_MY_STORE_ID") apiKey = os.Getenv("TELNYX_API_KEY") ) func kvGet(key string) (string, error) { url := fmt.Sprintf("https://api.telnyx.com/v2/storage/kvs/%s/keys/%s", kvID, key) req, _ := http.NewRequest("GET", url, nil) req.Header.Set("Authorization", "Bearer "+apiKey) resp, err := http.DefaultClient.Do(req) if err != nil { return "", err } defer resp.Body.Close() if resp.StatusCode == 404 { return "", nil } var result struct { Value string `json:"value"` } json.NewDecoder(resp.Body).Decode(&result) return result.Value, nil } func kvPut(key, value string, ttl int) error { url := fmt.Sprintf("https://api.telnyx.com/v2/storage/kvs/%s/keys/%s", kvID, key) body, _ := json.Marshal(map[string]interface{}{"value": value, "ttl": ttl}) req, _ := http.NewRequest("PUT", url, bytes.NewReader(body)) req.Header.Set("Authorization", "Bearer "+apiKey) req.Header.Set("Content-Type", "application/json") _, err := http.DefaultClient.Do(req) return err } func handler(w http.ResponseWriter, r *http.Request) { cached, _ := kvGet("user:123") if cached != "" { w.Header().Set("Content-Type", "application/json") fmt.Fprint(w, cached) return } userData := `{"id": 123, "name": "Alice"}` kvPut("user:123", userData, 3600) w.Header().Set("Content-Type", "application/json") fmt.Fprint(w, userData) } ``` ```python import os import httpx KV_ID = os.getenv("KV_MY_STORE_ID") API_KEY = os.getenv("TELNYX_API_KEY") BASE_URL = f"https://api.telnyx.com/v2/storage/kvs/{KV_ID}" def kv_get(key): response = httpx.get( f"{BASE_URL}/keys/{key}", headers={"Authorization": f"Bearer {API_KEY}"} ) if response.status_code == 404: return None return response.json().get("value") def kv_put(key, value, ttl=None): httpx.put( f"{BASE_URL}/keys/{key}", headers={"Authorization": f"Bearer {API_KEY}"}, json={"value": value, "ttl": ttl} ) def do_GET(self): cached = kv_get("user:123") if cached: self.send_response(200) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(cached.encode()) return user_data = '{"id": 123, "name": "Alice"}' kv_put("user:123", user_data, 3600) self.send_response(200) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(user_data.encode()) ``` ```java public class KVClient { private final String kvId = System.getenv("KV_MY_STORE_ID"); private final String apiKey = System.getenv("TELNYX_API_KEY"); private final HttpClient client = HttpClient.newHttpClient(); public String get(String key) throws Exception { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.telnyx.com/v2/storage/kvs/" + kvId + "/keys/" + key)) .header("Authorization", "Bearer " + apiKey) .GET() .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() == 404) return null; return response.body(); } public void put(String key, String value, int ttl) throws Exception { String body = String.format("{\"value\":\"%s\",\"ttl\":%d}", value, ttl); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.telnyx.com/v2/storage/kvs/" + kvId + "/keys/" + key)) .header("Authorization", "Bearer " + apiKey) .header("Content-Type", "application/json") .PUT(HttpRequest.BodyPublishers.ofString(body)) .build(); client.send(request, HttpResponse.BodyHandlers.ofString()); } } ``` ## Cron Trigger **Coming Soon** — Native cron triggers are planned. The examples below show the expected pattern. Until then, use an external scheduler (GitHub Actions, AWS EventBridge) to call your function's HTTP endpoint. Functions can be triggered on a schedule via [cron triggers](/docs/edge-compute/configuration/cron-triggers). The request includes cron metadata. ```javascript const http = require('http'); http.createServer(async (req, res) => { if (req.url === '/health/liveness' || req.url === '/health/readiness') { res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end('ok'); } const isCron = req.headers['x-telnyx-cron'] === 'true'; const scheduleName = req.headers['x-telnyx-cron-schedule'] || ''; if (isCron) { console.log(`Cron job triggered: ${scheduleName}`); // Your cron logic here res.writeHead(200, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ status: 'cron completed' })); } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ status: 'ok' })); }); server.listen(8080); ``` ```typescript import http from 'http'; const server = http.createServer(async (req: http.IncomingMessage, res: http.ServerResponse) => { if (req.url === '/health/liveness' || req.url === '/health/readiness') { res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end('ok'); } const isCron: boolean = req.headers['x-telnyx-cron'] === 'true'; const scheduleName: string = (req.headers['x-telnyx-cron-schedule'] as string) || ''; if (isCron) { console.log(`Cron job triggered: ${scheduleName}`); // Your cron logic here res.writeHead(200, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ status: 'cron completed' })); } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ status: 'ok' })); }); server.listen(8080); ``` ```go func handler(w http.ResponseWriter, r *http.Request) { isCron := r.Header.Get("X-Telnyx-Cron") == "true" scheduleName := r.Header.Get("X-Telnyx-Cron-Schedule") if isCron { log.Printf("Cron job triggered: %s", scheduleName) cleanupExpiredSessions() sendDailyReport() w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"status": "cron completed"}) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"status": "ok"}) } ``` ```python def do_GET(self): is_cron = self.headers.get("X-Telnyx-Cron") == "true" schedule_name = self.headers.get("X-Telnyx-Cron-Schedule") if is_cron: print(f"Cron job triggered: {schedule_name}") cleanup_expired_sessions() send_daily_report() self.send_response(200) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(json.dumps({"status": "cron completed"}).encode()) return self.send_response(200) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(json.dumps({"status": "ok"}).encode()) ``` ```java @GET @Produces(MediaType.APPLICATION_JSON) public Response handler(@HeaderParam("X-Telnyx-Cron") String isCron, @HeaderParam("X-Telnyx-Cron-Schedule") String scheduleName) { if ("true".equals(isCron)) { System.out.println("Cron job triggered: " + scheduleName); cleanupExpiredSessions(); sendDailyReport(); return Response.ok(Map.of("status", "cron completed")).build(); } return Response.ok(Map.of("status", "ok")).build(); } ``` ## WebSocket Handling **Coming Soon** — WebSocket support is planned. The examples below show the expected pattern. Handle WebSocket connections for real-time communication. ```go import ( "github.com/gorilla/websocket" ) var upgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, } func handler(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { http.Error(w, "WebSocket upgrade failed", http.StatusBadRequest) return } defer conn.Close() conn.WriteJSON(map[string]interface{}{ "type": "connected", "timestamp": time.Now().Unix(), }) for { var msg map[string]interface{} if err := conn.ReadJSON(&msg); err != nil { break } conn.WriteJSON(map[string]interface{}{ "type": "echo", "original": msg, "serverTime": time.Now().Unix(), }) } } ``` ```python import asyncio import websockets import json import time async def handler(websocket, path): await websocket.send(json.dumps({ "type": "connected", "timestamp": int(time.time()) })) async for message in websocket: data = json.loads(message) print(f"Received: {data}") await websocket.send(json.dumps({ "type": "echo", "original": data, "serverTime": int(time.time()) })) start_server = websockets.serve(handler, "0.0.0.0", 8080) asyncio.get_event_loop().run_until_complete(start_server) asyncio.get_event_loop().run_forever() ``` ```java import jakarta.websocket.*; import jakarta.websocket.server.ServerEndpoint; @ServerEndpoint("/ws") public class WebSocketHandler { @OnOpen public void onOpen(Session session) { System.out.println("Client connected: " + session.getId()); session.getAsyncRemote().sendText( "{\"type\":\"connected\",\"timestamp\":" + System.currentTimeMillis() + "}" ); } @OnMessage public void onMessage(String message, Session session) { System.out.println("Received: " + message); String response = String.format( "{\"type\":\"echo\",\"original\":%s,\"serverTime\":%d}", message, System.currentTimeMillis() ); session.getAsyncRemote().sendText(response); } @OnClose public void onClose(Session session) { System.out.println("Client disconnected: " + session.getId()); } } ``` ## Service-to-Service Calls Call other edge functions or external services with proper error handling and timeouts. ```javascript const http = require('http'); const INTERNAL_SERVICE_URL = process.env.USER_SERVICE_URL; http.createServer(async (req, res) => { if (req.url === '/health/liveness' || req.url === '/health/readiness') { res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end('ok'); } let body = ''; req.on('data', chunk => { body += chunk; }); req.on('end', async () => { try { const { userId } = JSON.parse(body); const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 5000); const userResponse = await fetch(`${INTERNAL_SERVICE_URL}/users/${userId}`, { headers: { 'Authorization': req.headers['authorization'], 'X-Request-ID': req.headers['x-request-id'] || crypto.randomUUID() }, signal: controller.signal }); clearTimeout(timeout); if (!userResponse.ok) { res.writeHead(userResponse.status, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ error: 'User service error' })); } const user = await userResponse.json(); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ user })); } catch (error) { if (error.name === 'AbortError') { res.writeHead(504, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ error: 'Service timeout' })); } res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Internal error' })); } }); }); server.listen(8080); ``` ```typescript import http from 'http'; const INTERNAL_SERVICE_URL: string | undefined = process.env.USER_SERVICE_URL; const server = http.createServer(async (req: http.IncomingMessage, res: http.ServerResponse) => { if (req.url === '/health/liveness' || req.url === '/health/readiness') { res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end('ok'); } let body: string = ''; req.on('data', (chunk: Buffer) => { body += chunk; }); req.on('end', async () => { try { const { userId }: { userId: string } = JSON.parse(body); const controller: AbortController = new AbortController(); const timeout: NodeJS.Timeout = setTimeout(() => controller.abort(), 5000); const userResponse: Response = await fetch(`${INTERNAL_SERVICE_URL}/users/${userId}`, { headers: { 'Authorization': req.headers['authorization'] as string, 'X-Request-ID': (req.headers['x-request-id'] as string) || crypto.randomUUID() }, signal: controller.signal }); clearTimeout(timeout); if (!userResponse.ok) { res.writeHead(userResponse.status, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ error: 'User service error' })); } const user: unknown = await userResponse.json(); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ user })); } catch (error: unknown) { if (error instanceof Error && error.name === 'AbortError') { res.writeHead(504, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ error: 'Service timeout' })); } res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Internal error' })); } }); }); server.listen(8080); ``` ```go var httpClient = &http.Client{ Timeout: 5 * time.Second, } func handler(w http.ResponseWriter, r *http.Request) { var input struct { UserID string `json:"userId"` } json.NewDecoder(r.Body).Decode(&input) userServiceURL := os.Getenv("USER_SERVICE_URL") req, _ := http.NewRequest("GET", userServiceURL+"/users/"+input.UserID, nil) req.Header.Set("Authorization", r.Header.Get("Authorization")) req.Header.Set("X-Request-ID", r.Header.Get("X-Request-ID")) userResp, err := httpClient.Do(req) if err != nil { w.WriteHeader(http.StatusGatewayTimeout) json.NewEncoder(w).Encode(map[string]string{"error": "Service timeout"}) return } defer userResp.Body.Close() if userResp.StatusCode != 200 { w.WriteHeader(userResp.StatusCode) json.NewEncoder(w).Encode(map[string]string{"error": "User service error"}) return } var user map[string]interface{} json.NewDecoder(userResp.Body).Decode(&user) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{"user": user}) } ``` ```python import os import httpx USER_SERVICE_URL = os.getenv("USER_SERVICE_URL") TIMEOUT = 5.0 async def handler(request): data = await request.json() user_id = data.get("userId") async with httpx.AsyncClient(timeout=TIMEOUT) as client: try: user_response = await client.get( f"{USER_SERVICE_URL}/users/{user_id}", headers={ "Authorization": request.headers.get("Authorization"), "X-Request-ID": request.headers.get("X-Request-ID", "") } ) user_response.raise_for_status() user = user_response.json() return {"user": user} except httpx.TimeoutException: return {"error": "Service timeout"}, 504 except httpx.HTTPStatusError as e: return {"error": "User service error"}, e.response.status_code ``` ```java @ApplicationScoped public class AggregatorResource { private final HttpClient client = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(5)) .build(); @ConfigProperty(name = "USER_SERVICE_URL") String userServiceUrl; @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response aggregate(Map input, @HeaderParam("Authorization") String auth, @HeaderParam("X-Request-ID") String requestId) { String userId = input.get("userId"); try { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(userServiceUrl + "/users/" + userId)) .header("Authorization", auth) .header("X-Request-ID", requestId) .timeout(Duration.ofSeconds(5)) .GET() .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { return Response.status(response.statusCode()) .entity(Map.of("error", "User service error")) .build(); } return Response.ok(Map.of("user", response.body())).build(); } catch (Exception e) { return Response.status(504) .entity(Map.of("error", "Service timeout")) .build(); } } } ``` ## Connection Reuse Initialize HTTP clients once at module level to reuse connections across requests. ```javascript const http = require('http'); // Module-level config (reused across requests) const httpClient = { baseUrl: 'https://api.example.com', headers: { 'Authorization': `Bearer ${process.env.API_KEY}`, 'Content-Type': 'application/json' } }; http.createServer(async (req, res) => { if (req.url === '/health/liveness' || req.url === '/health/readiness') { res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end('ok'); } const response = await fetch(`${httpClient.baseUrl}/data`, { headers: httpClient.headers }); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(await response.text()); }); server.listen(8080); ``` ```typescript import http from 'http'; // Module-level config (reused across requests) const httpClient: { baseUrl: string; headers: Record; } = { baseUrl: 'https://api.example.com', headers: { 'Authorization': `Bearer ${process.env.API_KEY}`, 'Content-Type': 'application/json' } }; const server = http.createServer(async (req: http.IncomingMessage, res: http.ServerResponse) => { if (req.url === '/health/liveness' || req.url === '/health/readiness') { res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end('ok'); } const response: Response = await fetch(`${httpClient.baseUrl}/data`, { headers: httpClient.headers }); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(await response.text()); }); server.listen(8080); ``` ```go // Package-level client (reused across requests) var httpClient = &http.Client{ Timeout: 10 * time.Second, Transport: &http.Transport{ MaxIdleConns: 100, MaxIdleConnsPerHost: 10, IdleConnTimeout: 90 * time.Second, }, } func handler(w http.ResponseWriter, r *http.Request) { resp, err := httpClient.Get("https://api.example.com/data") if err != nil { http.Error(w, "External API error", http.StatusBadGateway) return } defer resp.Body.Close() w.Header().Set("Content-Type", "application/json") io.Copy(w, resp.Body) } ``` ```python import urllib3 # Module-level connection pool (reused across requests) http_pool = urllib3.PoolManager( num_pools=10, maxsize=10, timeout=urllib3.Timeout(total=10.0) ) def do_GET(self): response = http_pool.request('GET', 'https://api.example.com/data') self.send_response(200) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(response.data) ``` ```java @ApplicationScoped public class EdgeResource { @Inject @RestClient ExternalApiClient apiClient; @GET @Produces(MediaType.APPLICATION_JSON) public Response handler() { var data = apiClient.fetchData(); return Response.ok(data).build(); } } ``` --- ## To Develop ### Runtime > Source: https://developers.telnyx.com/docs/edge-compute/runtime.md Edge Compute runs your functions in real Linux containers, giving you access to standard runtime APIs and system calls. Unlike V8 isolate-based platforms, you have a full POSIX environment with native language runtimes. ## Runtime Environment Your functions run in lightweight containers with: - **Full language runtimes** — Python 3.11+, Node.js 18+, Go 1.25+, Java 17+ (Quarkus) - **Standard libraries** — Use native packages and dependencies - **POSIX APIs** — File I/O, environment variables, process control - **Network access** — HTTP clients, TCP sockets, DNS resolution ## Platform APIs Edge Compute provides platform bindings for accessing Telnyx services: - [Bindings](/docs/edge-compute/runtime/bindings) — Connect to Telnyx APIs (Voice, Messaging, Storage) with auto-injected credentials - [Execution Model](/docs/edge-compute/runtime/execution-model) — Function lifecycle, cold starts, concurrency ## Accessing Environment Configuration is injected via environment variables: ```javascript // Environment variables from func.toml const logLevel = process.env.LOG_LEVEL || 'info'; const apiEndpoint = process.env.API_ENDPOINT; // Binding credentials (auto-injected) const telnyxKey = process.env.TELNYX_API_KEY; ``` ```go package main import "os" func main() { // Environment variables from func.toml logLevel := os.Getenv("LOG_LEVEL") apiEndpoint := os.Getenv("API_ENDPOINT") // Binding credentials (auto-injected) telnyxKey := os.Getenv("TELNYX_API_KEY") } ``` ```python import os # Environment variables from func.toml log_level = os.environ.get('LOG_LEVEL', 'info') api_endpoint = os.environ.get('API_ENDPOINT') # Binding credentials (auto-injected) telnyx_key = os.environ.get('TELNYX_API_KEY') ``` ```java public class Handler { // Environment variables from func.toml private static final String LOG_LEVEL = System.getenv("LOG_LEVEL"); private static final String API_ENDPOINT = System.getenv("API_ENDPOINT"); // Binding credentials (auto-injected) private static final String TELNYX_KEY = System.getenv("TELNYX_API_KEY"); } ``` ## HTTP Handling Functions receive HTTP requests and return responses: ```javascript const express = require('express'); const app = express(); app.use(express.json()); app.all('/', (req, res) => { if (req.method === 'POST') { return res.json({ received: req.body }); } res.json({ status: 'ok' }); }); app.listen(8080); ``` ```go package main import ( "encoding/json" "net/http" ) func handler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if r.Method == "POST" { var data map[string]interface{} json.NewDecoder(r.Body).Decode(&data) json.NewEncoder(w).Encode(map[string]interface{}{ "received": data, }) return } json.NewEncoder(w).Encode(map[string]string{ "status": "ok", }) } func main() { http.HandleFunc("/", handler) http.ListenAndServe(":8080", nil) } ``` ```python from flask import Flask, request, jsonify app = Flask(__name__) @app.route('/', methods=['GET', 'POST']) def handler(): if request.method == 'POST': data = request.get_json() return jsonify({"received": data}) return jsonify({"status": "ok"}) ``` ## Networking Functions can make outbound HTTP requests: ```javascript async function fetchData() { const response = await fetch('https://api.example.com/data'); return response.json(); } async function postData(payload) { const response = await fetch('https://api.example.com/submit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); return response.status; } ``` ```go package main import ( "bytes" "encoding/json" "net/http" "time" ) var client = &http.Client{Timeout: 10 * time.Second} func fetchData() (map[string]interface{}, error) { resp, err := client.Get("https://api.example.com/data") if err != nil { return nil, err } defer resp.Body.Close() var data map[string]interface{} json.NewDecoder(resp.Body).Decode(&data) return data, nil } ``` ```python import requests def fetch_data(): response = requests.get('https://api.example.com/data') return response.json() def post_data(payload): response = requests.post( 'https://api.example.com/submit', json=payload, timeout=10 ) return response.status_code ``` ## Next Steps - [Bindings](/docs/edge-compute/runtime/bindings) — Access Telnyx services from your functions - [Execution Model](/docs/edge-compute/runtime/execution-model) — Understand lifecycle and performance - [Configuration](/docs/edge-compute/configuration) — Set environment variables and secrets --- ### Bindings > Source: https://developers.telnyx.com/docs/edge-compute/runtime/bindings.md Bindings allow your edge functions to interact with Telnyx platform services. They provide secure, auto-authenticated access without managing API keys in your code. ## Available Bindings | Binding | Description | Status | |---------|-------------|--------| | [Voice](/docs/voice/programmable-voice) | Make and receive calls | ✅ Available | | [Messaging](/docs/messaging) | Send SMS/MMS | ✅ Available | | [Phone Numbers](/docs/numbers/phone-numbers) | Manage numbers | ✅ Available | | [Fax](/docs/programmable-fax) | Send faxes | ✅ Available | | [Verify](/docs/identity/verify) | 2FA/verification | ✅ Available | | [Cloud Storage](/docs/cloud-storage) | S3-compatible storage | ✅ Available | | KV | Key-value store | 🔜 Coming soon | | SQL DB | Serverless database | 🔜 Coming soon | ## What is a Binding? When you configure a binding for your function, you grant it the capability to access Telnyx services. The binding handles authentication automatically — your API key is never exposed in your code or logs. ``` Your Function → Binding Proxy → Telnyx API ↓ ↓ ↓ Placeholder Resolves Actual API API Key Credentials Call Made ``` When you deploy a function with an active binding: 1. Your function receives a JWT credential as the `TELNYX_API_KEY` environment variable 2. Your function receives the binding proxy URL as `TELNYX_BASE_URL` 3. API calls made to `${TELNYX_BASE_URL}/` are routed through the binding proxy, which authenticates with your real API key 4. The API call proceeds with proper authentication **Important:** You must construct API URLs using `TELNYX_BASE_URL` — do **not** call `https://api.telnyx.com` directly. Direct calls will return 401 because the binding-injected JWT is not a standard Telnyx API key. ```javascript // ✅ Correct — use TELNYX_BASE_URL from binding const baseUrl = process.env.TELNYX_BASE_URL; const response = await fetch(`${baseUrl}/messages`, { headers: { 'Authorization': `Bearer ${process.env.TELNYX_API_KEY}` } }); // ❌ Wrong — direct calls to api.telnyx.com return 401 with binding JWT const response = await fetch('https://api.telnyx.com/v2/messages', { headers: { 'Authorization': `Bearer ${process.env.TELNYX_API_KEY}` } }); ``` **Benefits:** - API key never appears in function code - API key never appears in logs - Credentials can be rotated without code changes ## Creating a Binding One-time setup per organization: ```bash # Create a binding for your organization telnyx-edge bindings create # Verify the binding works telnyx-edge bindings validate ``` ## Managing Bindings ```bash # View your current binding telnyx-edge bindings get # Validate the binding is working telnyx-edge bindings validate # Rotate binding credentials (recommended monthly) telnyx-edge bindings update # Remove the binding telnyx-edge bindings delete ``` --- ## Using Bindings: Telnyx API With a binding configured, use `TELNYX_BASE_URL` and `TELNYX_API_KEY` to make API calls through the binding proxy. The proxy handles authentication with your real API key. ```javascript const http = require('http'); const BASE_URL = process.env.TELNYX_BASE_URL; const API_KEY = process.env.TELNYX_API_KEY; http.createServer(async (req, res) => { if (req.url === '/health/liveness' || req.url === '/health/readiness') { res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end('ok'); } // Send a message via binding proxy const response = await fetch(`${BASE_URL}/messages`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ from: '+12345678901', to: '+12345678902', text: 'Hello from Edge Compute!' }) }); const data = await response.json(); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(data)); }).listen(8080); ``` ```go package main import ( "fmt" "github.com/telnyx/telnyx-go" ) func main() { // SDK automatically uses TELNYX_API_KEY and TELNYX_BASE_URL client := telnyx.NewClient() // Get account balance balance, err := client.Balance.Get() if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Printf("Balance: %s %s\n", balance.Balance, balance.Currency) } func sendMessage(to, from, text string) error { client := telnyx.NewClient() message, err := client.Messages.Create(&telnyx.MessageParams{ From: from, To: to, Text: text, }) if err != nil { return err } fmt.Printf("Message sent: %s\n", message.ID) return nil } ``` ```python import telnyx # SDK automatically uses TELNYX_API_KEY and TELNYX_BASE_URL # No manual configuration needed! def get_balance(): """Retrieve account balance.""" balance = telnyx.Balance.retrieve() return { "balance": balance.balance, "currency": balance.currency } def send_message(to_number, from_number, text): """Send an SMS using Telnyx SDK.""" message = telnyx.Message.create( from_=from_number, to=to_number, text=text ) return {"message_id": message.id, "status": message.status} def make_call(to_number, from_number, webhook_url): """Initiate an outbound call.""" call = telnyx.Call.create( to=to_number, from_=from_number, connection_id="your-connection-id", webhook_url=webhook_url ) return {"call_id": call.id} ``` ```java import com.telnyx.sdk.ApiClient; import com.telnyx.sdk.api.MessagesApi; import com.telnyx.sdk.model.CreateMessageRequest; @ApplicationScoped public class MessageResource { // SDK automatically uses TELNYX_API_KEY and TELNYX_BASE_URL @Inject ApiClient telnyxClient; @POST @Path("/send") @Produces(MediaType.APPLICATION_JSON) public Response sendMessage(MessageRequest req) { MessagesApi api = new MessagesApi(telnyxClient); CreateMessageRequest request = new CreateMessageRequest() .from(req.getFrom()) .to(req.getTo()) .text(req.getText()); var message = api.createMessage(request); return Response.ok(message).build(); } } ``` --- ## Using Bindings: Cloud Storage **The binding-injected `TELNYX_API_KEY` (a JWT) does not work for S3-compatible Cloud Storage operations.** The Cloud Storage endpoint (`*.telnyxcloudstorage.com`) requires AWS SigV4 authentication with a valid AWS access key — the binding JWT is not a valid AWS credential and will be rejected. For Cloud Storage operations (ListObjectsV2, GetObject, PutObject, etc.), store a regular Telnyx API key as a secret and use it as the S3 access key: ```bash telnyx-edge secrets add TELNYX_STORAGE_KEY YOUR_API_KEY ``` The binding proxy handles `api.telnyx.com` routes only. S3-compatible calls go directly to the storage endpoint. ### Using Cloud Storage with a Secret ```go package main import ( "os" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" ) func getStorageClient() *s3.S3 { // Use a real API key stored as a secret, NOT the binding TELNYX_API_KEY apiKey := os.Getenv("TELNYX_STORAGE_KEY") // set via: telnyx-edge secrets add if apiKey == "" { apiKey = os.Getenv("TELNYX_API_KEY") // fallback: only works with real API key, not binding JWT } sess := session.Must(session.NewSession(&aws.Config{ Endpoint: aws.String("https://us-east-1.telnyxcloudstorage.com"), Region: aws.String("us-east-1"), Credentials: credentials.NewStaticCredentials( apiKey, "", // Not required for Telnyx "", ), })) return s3.New(sess) } func listObjects(bucket string) ([]string, error) { client := getStorageClient() result, err := client.ListObjectsV2(&s3.ListObjectsV2Input{ Bucket: aws.String(bucket), }) if err != nil { return nil, err } var keys []string for _, obj := range result.Contents { keys = append(keys, *obj.Key) } return keys, nil } ``` ```python import os import boto3 def get_storage_client(): """Create a Cloud Storage client using a stored API key secret.""" # Use a real API key stored as a secret, NOT the binding TELNYX_API_KEY api_key = os.environ.get('TELNYX_STORAGE_KEY') # set via: telnyx-edge secrets add if not api_key: api_key = os.environ.get('TELNYX_API_KEY') # fallback: only works with real API key, not binding JWT return boto3.client( 's3', aws_access_key_id=api_key, aws_secret_access_key='', # Not required for Telnyx endpoint_url='https://us-east-1.telnyxcloudstorage.com' ) def upload_file(bucket, key, data): """Upload a file to Cloud Storage.""" client = get_storage_client() client.put_object(Bucket=bucket, Key=key, Body=data) return {"uploaded": key} def download_file(bucket, key): """Download a file from Cloud Storage.""" client = get_storage_client() response = client.get_object(Bucket=bucket, Key=key) return response['Body'].read() def list_objects(bucket, prefix=''): """List objects in a bucket.""" client = get_storage_client() response = client.list_objects_v2(Bucket=bucket, Prefix=prefix) return [obj['Key'] for obj in response.get('Contents', [])] ``` The binding proxy handles `api.telnyx.com` routes (Voice, Messaging, etc.) only. For S3-compatible operations against the Cloud Storage endpoint, use a regular Telnyx API key stored via `telnyx-edge secrets add`. --- ## Using Bindings: KV 🔜 **Coming soon** — KV bindings will allow key-value storage directly from edge functions. --- ## Using Bindings: SQL DB 🔜 **Coming soon** — SQL DB bindings will provide serverless database access from edge functions. --- ## Bindings vs Secrets | Feature | Bindings | Secrets | |---------|----------|---------| | **Purpose** | Telnyx service access | General sensitive data | | **Scope** | One per organization | Unlimited per organization | | **Credentials** | Auto-managed (JWT via proxy) | User-provided | | **Injection** | `TELNYX_API_KEY` (JWT), `TELNYX_BASE_URL` (proxy URL) | Custom environment variables | | **Rotation** | `bindings update` | Manual re-add | | **Use case** | Telnyx SDK/API integration | Database passwords, external API keys | **Use bindings** for Telnyx service access — credentials are managed automatically. **Use [secrets](/docs/edge-compute/configuration/secrets)** for third-party services or when you need multiple different credentials. --- ## Error Handling ```python import telnyx from telnyx.error import TelnyxError, AuthenticationError, APIError def safe_api_call(): try: balance = telnyx.Balance.retrieve() return {"balance": balance.balance} except AuthenticationError as e: # Binding issue - credentials invalid or expired return { "error": "Authentication failed", "hint": "Try: telnyx-edge bindings update" } except APIError as e: # API-level error return { "error": f"API error: {e.message}", "code": e.code } except TelnyxError as e: # General Telnyx error return {"error": str(e)} ``` --- ## Troubleshooting ### Binding not found ```bash $ telnyx-edge bindings get ❌ No binding found # Solution: Create a binding $ telnyx-edge bindings create ``` ### Validation failed ```bash $ telnyx-edge bindings validate ❌ Binding validation failed # Solutions: # 1. Update the binding telnyx-edge bindings update # 2. Check your Telnyx account status # 3. Verify authentication telnyx-edge auth status ``` ### Function can't access Telnyx API ```bash # Check binding exists telnyx-edge bindings get # Validate binding works telnyx-edge bindings validate # Redeploy function to get latest credentials telnyx-edge ship ``` --- ### Execution Model > Source: https://developers.telnyx.com/docs/edge-compute/runtime/execution-model.md Understanding how Edge Compute executes your functions helps you optimize performance and build reliable applications. ## Overview Edge Compute runs your functions in lightweight Linux containers. When a request arrives: 1. **Routing** — Request is routed to the nearest edge location 2. **Container selection** — An existing warm container handles the request, or a new one starts (cold start) 3. **Execution** — Your function processes the request 4. **Response** — Result is returned to the caller 5. **Keep-alive** — Container stays warm for subsequent requests ## Function Lifecycle ### Cold Start Phase When a function receives its first request or scales up, a new container initializes: ```python # Global initialization (runs once per container) import json from database import create_pool # Expensive operations here — only run once db_pool = create_pool() cache = {} # Function handler (runs per request) async def handler(request): # Fast path — reuse initialized resources data = await request.json() result = await db_pool.query('SELECT * FROM users') return Response(json.dumps(result)) ``` ```go package function import ( "database/sql" "encoding/json" "log" "net/http" "os" ) // Global initialization (runs once per container) var db *sql.DB func init() { // Expensive operations here — only run once var err error db, err = sql.Open("postgres", os.Getenv("DATABASE_URL")) if err != nil { log.Fatal(err) } } // Function handler (runs per request) func Handle(w http.ResponseWriter, r *http.Request) { // Fast path — reuse initialized resources rows, err := db.Query("SELECT * FROM users") if err != nil { http.Error(w, err.Error(), 500) return } defer rows.Close() var users []map[string]interface{} cols, _ := rows.Columns() for rows.Next() { vals := make([]interface{}, len(cols)) ptrs := make([]interface{}, len(cols)) for i := range vals { ptrs[i] = &vals[i] } rows.Scan(ptrs...) row := make(map[string]interface{}) for i, col := range cols { row[col] = vals[i] } users = append(users, row) } json.NewEncoder(w).Encode(users) } ``` ```java import io.quarkus.funqy.Funq; import jakarta.inject.Inject; import io.agroal.api.AgroalDataSource; import java.sql.ResultSet; import java.util.*; public class Function { @Inject AgroalDataSource dataSource; @Funq public List> getUsers() throws Exception { try (var conn = dataSource.getConnection(); var stmt = conn.createStatement(); var rs = stmt.executeQuery("SELECT * FROM users")) { List> users = new ArrayList<>(); var meta = rs.getMetaData(); int cols = meta.getColumnCount(); while (rs.next()) { Map row = new HashMap<>(); for (int i = 1; i <= cols; i++) { row.put(meta.getColumnName(i), rs.getObject(i)); } users.add(row); } return users; } } } ``` **Cold start timeline:** 1. Container image pulled (cached at edge) 2. Runtime initializes (Python/Go/Quarkus) 3. Global code executes (imports, connections) 4. First request handled ### Warm Execution Subsequent requests reuse the same container instance: ```python import httpx # Global variables persist between requests client = httpx.Client(timeout=10.0) async def handler(request): # Fast execution — resources already initialized print(f"Request: {request.method} {request.path}") resp = client.get("https://api.example.com/data") return resp.json() ``` ```go package function import ( "encoding/json" "log" "net/http" "os" "time" ) // Global variables persist between requests var ( client *http.Client logger *log.Logger ) func init() { // One-time initialization client = &http.Client{Timeout: 10 * time.Second} logger = log.New(os.Stdout, "[EDGE] ", log.LstdFlags) } func Handle(w http.ResponseWriter, r *http.Request) { // Fast execution — resources already initialized logger.Printf("Request: %s %s", r.Method, r.URL.Path) resp, err := client.Get("https://api.example.com/data") if err != nil { http.Error(w, err.Error(), 500) return } defer resp.Body.Close() var data map[string]interface{} json.NewDecoder(resp.Body).Decode(&data) json.NewEncoder(w).Encode(data) } ``` ```java import io.quarkus.funqy.Funq; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.net.URI; public class Function { private static final HttpClient client = HttpClient.newHttpClient(); @Funq public String fetchData(String path) throws Exception { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.example.com" + path)) .timeout(java.time.Duration.ofSeconds(10)) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); return response.body(); } } ``` ### Container Recycling Containers are recycled when: - Idle for extended periods (to free resources) - Memory limits approached - New deployment shipped - Platform scaling decisions Don't rely on container persistence for critical state. Use [KV](/docs/edge-compute/kv) or external storage for data that must survive restarts. ## Cold Start Optimization Minimize cold start latency with these patterns: ### 1. Lazy Initialization Defer expensive operations until needed: ```python # Bad — always initializes ml_model = load_model('large_model.pkl') # 2 seconds def handler(request): return ml_model.predict(request.data) ``` ```python # Good — only loads when needed _ml_model = None def get_model(): global _ml_model if _ml_model is None: _ml_model = load_model('large_model.pkl') return _ml_model def handler(request): if request.path == '/predict': return get_model().predict(request.data) return {"status": "ok"} ``` ```go // Bad — always initializes var mlModel = loadModel("large_model.pkl") // 2 seconds func Handle(w http.ResponseWriter, r *http.Request) { result := mlModel.Predict(r.Body) json.NewEncoder(w).Encode(result) } ``` ```go // Good — only loads when needed var ( mlModel *Model modelOnce sync.Once ) func getModel() *Model { modelOnce.Do(func() { mlModel = loadModel("large_model.pkl") }) return mlModel } func Handle(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/predict" { result := getModel().Predict(r.Body) json.NewEncoder(w).Encode(result) return } json.NewEncoder(w).Encode(map[string]string{"status": "ok"}) } ``` ```java // Bad — always initializes at class load static final Model model = Model.load("large_model.pkl"); @Funq public Result predict(Input input) { return model.predict(input); } ``` ```java // Good — only loads when needed import java.util.function.Supplier; private static final Supplier model = Suppliers.memoize(() -> Model.load("large_model.pkl") ); @Funq public Result predict(Input input) { return model.get().predict(input); } @Funq public String status() { return "ok"; // No model load if just checking status } ``` ### 2. Minimize Dependencies ```python # Bad — imports everything import pandas as pd import numpy as np import tensorflow as tf # Good — import only what you need from json import loads, dumps ``` ```go // Bad — imports everything import ( "github.com/heavy/ml-library" "github.com/huge/data-processing" ) // Good — import only what you need import ( "encoding/json" "net/http" ) ``` ```java // Bad — imports heavy dependencies import com.fasterxml.jackson.databind.*; // Good — use built-in JSON handling import jakarta.json.Json; import jakarta.json.JsonObject; ``` ### 3. Connection Pooling ```python # Initialize pool globally from sqlalchemy import create_engine from sqlalchemy.pool import QueuePool engine = create_engine( DATABASE_URL, poolclass=QueuePool, pool_size=5, max_overflow=10 ) def handler(request): with engine.connect() as conn: result = conn.execute("SELECT * FROM users") return list(result) ``` ```go // Initialize pool globally var db *sql.DB func init() { var err error db, err = sql.Open("postgres", os.Getenv("DATABASE_URL")) if err != nil { log.Fatal(err) } db.SetMaxOpenConns(10) db.SetMaxIdleConns(5) } func Handle(w http.ResponseWriter, r *http.Request) { rows, err := db.Query("SELECT * FROM users") if err != nil { http.Error(w, err.Error(), 500) return } defer rows.Close() // ... } ``` ```java // Quarkus automatically configures connection pooling with Agroal // Add to application.properties: // quarkus.datasource.jdbc.url=jdbc:postgresql://... // quarkus.datasource.jdbc.max-size=20 @ApplicationScoped public class UserService { @Inject AgroalDataSource ds; public List getUsers() throws SQLException { try (var conn = ds.getConnection(); var stmt = conn.createStatement(); var rs = stmt.executeQuery("SELECT * FROM users")) { // ... } } } ``` ## Concurrency Each container handles **one request at a time** by default. The platform automatically scales containers based on traffic: ``` Traffic: 100 requests/second ↓ Platform scales to ~100 containers ↓ Each container handles ~1 req/sec ``` ### Scaling Behavior | Traffic Pattern | Platform Response | |-----------------|-------------------| | Traffic spike | New containers start (cold starts) | | Sustained load | Containers stay warm | | Traffic drops | Containers gradually recycle | | Zero traffic | All containers recycle after idle timeout | ## Request Timeouts Functions have execution time limits: | Tier | Timeout | |------|---------| | Default | 30 seconds | | Extended | 60 seconds (configurable) | Handle timeouts gracefully: ```python import asyncio async def handler(request): try: # Set timeout for 25 seconds (5 sec buffer before platform timeout) result = await asyncio.wait_for( long_running_operation(), timeout=25.0 ) return result except asyncio.TimeoutError: return {"error": "Operation timed out", "partial": get_partial_result()} ``` ```go func Handle(w http.ResponseWriter, r *http.Request) { // Create context with timeout (5 sec buffer before platform timeout) ctx, cancel := context.WithTimeout(r.Context(), 25*time.Second) defer cancel() resultCh := make(chan Result, 1) go func() { resultCh <- longRunningOperation(ctx) }() select { case result := <-resultCh: json.NewEncoder(w).Encode(result) case <-ctx.Done(): json.NewEncoder(w).Encode(map[string]string{ "error": "Operation timed out", }) } } ``` ```java import io.smallrye.mutiny.Uni; import java.time.Duration; @Funq public Uni handleRequest(Input input) { return Uni.createFrom().item(() -> longRunningOperation(input)) .onItem().ifNoItem().after(Duration.ofSeconds(25)) .failWith(new TimeoutException("Operation timed out")); } ``` ## Triggers Functions can be invoked by: ### HTTP Requests ```toml # func.toml [edge_compute] func_name = "my-api" # Accessible at: https://my-api-{orgId}.telnyxcompute.com ``` ### Webhooks Configure Telnyx services to call your function: ```python async def handler(request): event = await request.json() if event.get('event_type') == 'message.received': handle_incoming_message(event['data']) elif event.get('event_type') == 'call.initiated': handle_call(event['data']) return {"status": "ok"} ``` ```go func Handle(w http.ResponseWriter, r *http.Request) { var event WebhookEvent json.NewDecoder(r.Body).Decode(&event) switch event.EventType { case "message.received": handleIncomingMessage(event.Data) case "call.initiated": handleCall(event.Data) } json.NewEncoder(w).Encode(map[string]string{"status": "ok"}) } ``` ```java import io.quarkus.funqy.Funq; import java.util.Map; public class WebhookHandler { @Funq public Map handle(WebhookEvent event) { switch (event.eventType) { case "message.received": handleIncomingMessage(event.data); break; case "call.initiated": handleCall(event.data); break; } return Map.of("status", "ok"); } } ``` ### Cron Triggers (Coming Soon) 🔜 Scheduled execution via cron expressions is planned for a future release. ## Graceful Shutdown When containers recycle, your function receives a shutdown signal: ```python import signal import sys def shutdown_handler(signum, frame): # Clean up resources db_pool.close() cache.flush() sys.exit(0) signal.signal(signal.SIGTERM, shutdown_handler) ``` ```go func init() { // Set up graceful shutdown stop := make(chan os.Signal, 1) signal.Notify(stop, syscall.SIGTERM) go func() { <-stop log.Println("Shutting down...") db.Close() os.Exit(0) }() } ``` ```java import io.quarkus.runtime.ShutdownEvent; import jakarta.enterprise.event.Observes; public class ShutdownHandler { void onShutdown(@Observes ShutdownEvent event) { // Clean up resources closeConnections(); flushCache(); } } ``` ## Best Practices 1. **Initialize globally** — Move expensive setup outside request handlers 2. **Keep handlers fast** — Aim for < 100ms p99 latency 3. **Use connection pools** — Reuse database/HTTP connections 4. **Handle errors gracefully** — Return meaningful error responses 5. **Don't store state in memory** — Use KV or external storage for persistence 6. **Set appropriate timeouts** — On outbound requests to prevent hanging ## Next Steps - [Bindings](/docs/edge-compute/runtime/bindings) — Connect to Telnyx platform services - [Limits](/docs/edge-compute/reference/limits) — Understand resource constraints - [Configuration](/docs/edge-compute/configuration) — Set environment variables and secrets --- ### Local Development > Source: https://developers.telnyx.com/docs/edge-compute/development.md Develop and test your Edge Compute functions on your local machine before deploying to Telnyx infrastructure. Run your function code locally using your language's native tools, then deploy with `telnyx-edge ship`. ## Development Workflow The local development workflow: 1. **Create function** with `telnyx-edge new-func` 2. **Develop locally** using native language tools 3. **Test** with your language's test framework 4. **Deploy** with `telnyx-edge ship` ## Quick Start Create a new function and start developing: ```bash # Create a new function telnyx-edge new-func -l=python -n=my-function cd my-function ``` ## Running Locally by Language The scaffolded Go function uses the `function` package. To test locally, create a test harness: ```go // handler_test.go package function import ( "net/http" "net/http/httptest" "testing" ) func TestHandle(t *testing.T) { req := httptest.NewRequest("GET", "/", nil) rec := httptest.NewRecorder() Handle(rec, req) if rec.Code != 200 { t.Errorf("Expected status 200, got %d", rec.Code) } } ``` Run tests: ```bash go test ./... ``` For local HTTP testing, you can create a temporary `main.go`: ```go // +build ignore package main import ( "net/http" "example.com/hello-world/function" ) func main() { http.HandleFunc("/", function.Handle) http.ListenAndServe(":8080", nil) } ``` Run with: ```bash go run main.go ``` Python functions use ASGI protocol. To test locally, create an ASGI app wrapper and use `uvicorn`: ```bash # Install uvicorn pip install uvicorn # Create an ASGI app wrapper # In app.py: ``` ```python # app.py - ASGI wrapper for local testing from function import new # new() returns a Function instance, .handle is the ASGI app func = new() app = func.handle ``` ```bash # Run with: uvicorn app:app --port 8080 ``` Or create a simple test script: ```python # test_local.py import asyncio import json from function import Function async def test_handle(): func = Function() responses = [] async def receive(): return {"type": "http.request", "body": b""} async def send(message): responses.append(message) scope = { "type": "http", "method": "GET", "path": "/", "headers": [], } await func.handle(scope, receive, send) print("Response:", responses) # Should contain response.start and response.body asyncio.run(test_handle()) ``` Run with: ```bash python test_local.py ``` Quarkus functions can be tested using Quarkus test mode: ```bash # Run tests ./mvnw test # Or with Maven mvn test ``` Create a test class: ```java // src/test/java/com/telnyx/edge/FunctionTest.java package com.telnyx.edge; import io.quarkus.test.junit.QuarkusTest; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @QuarkusTest public class FunctionTest { @Test public void testHello() { Function func = new Function(); var result = func.hello(null); assertNotNull(result); assertEquals("success", result.get("status")); } } ``` Run with: ```bash ./mvnw test ``` ## Environment Variables During local development, set environment variables in your shell or a `.env` file: ```bash # Option 1: Export in shell export LOG_LEVEL=debug export API_URL=https://api.example.com # Option 2: Use a .env file echo 'LOG_LEVEL=debug' >> .env echo 'API_URL=https://api.example.com' >> .env ``` ```go // Install godotenv // go get github.com/joho/godotenv import ( "os" "github.com/joho/godotenv" ) func init() { godotenv.Load() // Load .env file } func getLogLevel() string { return os.Getenv("LOG_LEVEL") } ``` ```python # Install python-dotenv # pip install python-dotenv from dotenv import load_dotenv load_dotenv() # Load .env file import os log_level = os.getenv("LOG_LEVEL", "info") ``` ```java // Quarkus supports .env files automatically // Or use application.properties // LOG_LEVEL=debug import org.eclipse.microprofile.config.inject.ConfigProperty; import jakarta.inject.Inject; public class Function { @ConfigProperty(name = "LOG_LEVEL", defaultValue = "info") String logLevel; } ``` Add `.env` to your `.gitignore` to avoid committing local values. ## Secrets in Local Development For local development, mock secrets with environment variables: ```bash # Set secrets locally export MY_API_KEY="test-key-12345" export DATABASE_URL="postgres://localhost:5432/devdb" ``` Your code accesses them the same way it would in production: ```python import os api_key = os.getenv("MY_API_KEY") ``` Never commit actual secret values. Use test/mock values locally and set real secrets with `telnyx-edge secrets add` for production. ## Testing Requests Once your local server is running, test with curl: ```bash curl http://localhost:8080/ ``` ```bash curl -X POST http://localhost:8080/api/data \ -H "Content-Type: application/json" \ -d '{"key": "value"}' ``` ```bash curl http://localhost:8080/api/protected \ -H "Authorization: Bearer test-token" \ -H "X-Custom-Header: value" ``` ## Unit Testing Write unit tests using your language's test framework: ```go // handler_test.go package function import ( "net/http" "net/http/httptest" "strings" "testing" ) func TestHandleGET(t *testing.T) { req := httptest.NewRequest("GET", "/", nil) rec := httptest.NewRecorder() Handle(rec, req) if rec.Code != 200 { t.Errorf("Expected 200, got %d", rec.Code) } } func TestHandlePOST(t *testing.T) { req := httptest.NewRequest("POST", "/", strings.NewReader(`{"name":"test"}`)) rec := httptest.NewRecorder() Handle(rec, req) if rec.Code != 200 && rec.Code != 201 { t.Errorf("Expected 200 or 201, got %d", rec.Code) } } ``` Run tests: ```bash go test ./... ``` ```python # tests/test_handler.py import pytest import asyncio from function import Function @pytest.mark.asyncio async def test_handle_get(): func = Function() responses = [] async def receive(): return {"type": "http.request", "body": b""} async def send(message): responses.append(message) scope = {"type": "http", "method": "GET", "path": "/", "headers": []} await func.handle(scope, receive, send) # Check response.start assert responses[0]["type"] == "http.response.start" assert responses[0]["status"] == 200 ``` Run tests: ```bash pip install pytest pytest-asyncio pytest tests/ ``` ```java // src/test/java/com/telnyx/edge/FunctionTest.java package com.telnyx.edge; import io.quarkus.test.junit.QuarkusTest; import org.junit.jupiter.api.Test; import java.util.Map; import static org.junit.jupiter.api.Assertions.*; @QuarkusTest public class FunctionTest { @Test public void testHelloReturnsSuccess() { Function func = new Function(); Map result = func.hello(Map.of("test", "data")); assertEquals("success", result.get("status")); assertNotNull(result.get("message")); } } ``` Run tests: ```bash ./mvnw test ``` ## Differences from Production Local development differs from production in some ways: | Feature | Local | Production | |---------|-------|------------| | **Network** | localhost | Global edge network | | **Secrets** | Environment variables | Encrypted storage | | **Bindings** | Mocked/simulated | Connected to services | | **Cold starts** | N/A | Container initialization | | **Resource limits** | Your machine | [Platform limits](/docs/edge-compute/reference/limits) | ## Deploy When Ready Once your function works locally, deploy to Telnyx: ```bash telnyx-edge ship ``` Your function is now live on the edge network. ## Best Practices ### Project Structure ``` my-function/ ├── func.toml # Function configuration ├── function/ # Function code (Python) │ └── __init__.py ├── pyproject.toml # Dependencies (Python) ├── .env # Local env vars (gitignored) ├── .gitignore └── tests/ └── test_handler.py # Unit tests ``` ### Git Ignore ```gitignore # Local development .env .env.local # Dependencies __pycache__/ node_modules/ vendor/ # Build artifacts dist/ build/ *.pyc target/ ``` ### Keep Parity - Use the same language version locally as production - Match dependency versions - Test with production-like request payloads ## Related Resources - [CLI Reference](/docs/edge-compute/reference/cli) — All CLI commands - [Testing](/docs/edge-compute/testing) — Testing strategies - [CI/CD](/docs/edge-compute/deploy) — Automated deployments --- ### Framework Support > Source: https://developers.telnyx.com/docs/edge-compute/frameworks-support.md **Coming Soon** — Framework auto-detection and adapters are in development. Framework support will let you deploy applications built with popular web frameworks to Edge Compute with minimal changes. ## Related Resources - [Quick Start](/docs/edge-compute/quickstart) — Deploy your first function - [Examples](/docs/edge-compute/examples) — Sample functions --- ### Best Practices > Source: https://developers.telnyx.com/docs/edge-compute/best-practices.md Best practices for Edge Compute based on production patterns and common issues. ## Configuration ### Use Environment Variables for Secrets Never hardcode secrets. Use the secrets API: ```bash telnyx-edge secrets add --name API_KEY --value "sk-..." ``` Access in code: ```javascript const apiKey = process.env.API_KEY; ``` ### Set Appropriate Timeouts Configure timeouts based on your function's needs: ```toml # func.toml [edge_compute] timeout_seconds = 30 # Default: 30, Max: 300 ``` ### Use Descriptive Function Names Function names become part of your URL. Choose wisely: ```bash # ✅ Good telnyx-edge new-func --name=user-api telnyx-edge new-func --name=webhook-handler # ❌ Bad telnyx-edge new-func --name=func1 telnyx-edge new-func --name=test ``` ## Performance ### Reuse Connections Initialize clients once at module level, not per request: ```javascript // ✅ Good — client initialized once, reused across requests const client = new HttpClient({ timeout: 5000 }); export async function handler(request) { const response = await client.get('https://api.example.com/data'); return new Response(JSON.stringify(response)); } ``` ```javascript // ❌ Bad — new client created every request export async function handler(request) { const client = new HttpClient({ timeout: 5000 }); const response = await client.get('https://api.example.com/data'); return new Response(JSON.stringify(response)); } ``` ```go // ✅ Good — client at package level var httpClient = &http.Client{ Timeout: 5 * time.Second, } func handler(w http.ResponseWriter, r *http.Request) { resp, _ := httpClient.Get("https://api.example.com/data") // ... } ``` ```python # ✅ Good — client initialized once import httpx client = httpx.AsyncClient(timeout=5.0) async def handler(request): response = await client.get('https://api.example.com/data') return {"body": response.text} ``` ### Minimize Cold Starts Cold starts happen when a new container spins up. Reduce their impact: 1. **Lazy-load dependencies** — Only import what you need, when you need it 2. **Keep functions small** — Smaller code = faster load 3. **Use lightweight frameworks** — Hono over Express, FastAPI over Django ```javascript // ✅ Good — lazy load heavy dependency let heavyLib; function getHeavyLib() { if (!heavyLib) { heavyLib = require('heavy-library'); } return heavyLib; } export async function handler(request) { if (needsHeavyProcessing(request)) { const lib = getHeavyLib(); // use lib } } ``` ### Cache Expensive Operations Use KV for caching API responses and computed data: ```javascript async function getUser(userId) { // Check cache first const cached = await kv.get(`user:${userId}`); if (cached) return JSON.parse(cached); // Fetch from origin const user = await fetchUserFromDB(userId); // Cache for 5 minutes await kv.put(`user:${userId}`, JSON.stringify(user), { ttl: 300 }); return user; } ``` ## Reliability ### Handle Errors Gracefully Always return proper error responses: ```javascript export async function handler(request) { try { const data = await riskyOperation(); return new Response(JSON.stringify(data), { headers: { "Content-Type": "application/json" } }); } catch (error) { console.error("Operation failed:", error.message); return new Response(JSON.stringify({ error: "Internal server error", requestId: request.headers.get("X-Request-ID") }), { status: 500, headers: { "Content-Type": "application/json" } }); } } ``` ### Set Timeouts on External Calls Don't let slow external services hang your function: ```javascript const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 5000); try { const response = await fetch('https://api.example.com/data', { signal: controller.signal }); clearTimeout(timeout); return response; } catch (error) { if (error.name === 'AbortError') { return new Response('External service timeout', { status: 504 }); } throw error; } ``` ### Implement Retries with Backoff For critical operations, add retry logic: ```javascript async function fetchWithRetry(url, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { const response = await fetch(url); if (response.ok) return response; // Don't retry client errors if (response.status < 500) throw new Error(`HTTP ${response.status}`); } catch (error) { if (i === maxRetries - 1) throw error; // Exponential backoff await new Promise(r => setTimeout(r, Math.pow(2, i) * 100)); } } } ``` ## Security ### Validate Input Never trust user input: ```javascript export async function handler(request) { const body = await request.json(); // Validate required fields if (!body.email || typeof body.email !== 'string') { return new Response(JSON.stringify({ error: 'Invalid email' }), { status: 400 }); } // Validate format if (!isValidEmail(body.email)) { return new Response(JSON.stringify({ error: 'Invalid email format' }), { status: 400 }); } // Sanitize const email = body.email.toLowerCase().trim(); // ... } ``` ### Use HTTPS for External Calls Always use HTTPS when calling external services: ```javascript // ✅ Good await fetch('https://api.example.com/data'); // ❌ Bad — insecure await fetch('http://api.example.com/data'); ``` ### Don't Log Sensitive Data Be careful what you log: ```javascript // ✅ Good — log request metadata console.log(`Request: ${request.method} ${request.url}`); // ❌ Bad — logging secrets console.log(`API Key: ${process.env.API_KEY}`); // ❌ Bad — logging full request body (may contain PII) console.log(`Body: ${JSON.stringify(body)}`); ``` ## Observability ### Add Request IDs Track requests through your system: ```javascript export async function handler(request) { const requestId = request.headers.get('X-Request-ID') || crypto.randomUUID(); console.log(`[${requestId}] Processing request`); // Include in response return new Response(JSON.stringify({ data }), { headers: { "Content-Type": "application/json", "X-Request-ID": requestId } }); } ``` ### Log at Appropriate Levels Use log levels effectively: ```javascript const logger = { debug: (msg) => process.env.LOG_LEVEL === 'debug' && console.log(`[DEBUG] ${msg}`), info: (msg) => console.log(`[INFO] ${msg}`), warn: (msg) => console.warn(`[WARN] ${msg}`), error: (msg) => console.error(`[ERROR] ${msg}`) }; // Use appropriately logger.debug(`Cache hit for key: ${key}`); // Verbose debugging logger.info(`User ${userId} created`); // Normal operations logger.warn(`Retry attempt ${i} for ${url}`); // Concerning but handled logger.error(`Database connection failed`); // Errors requiring attention ``` ## Related Resources - [Framework Support](/docs/edge-compute/frameworks-support) — Deploy apps built with popular frameworks - [Testing](/docs/edge-compute/testing) — Test your functions before deploying - [Observability](/docs/edge-compute/observability) — Monitor your functions --- ## To Test ### Testing > Source: https://developers.telnyx.com/docs/edge-compute/testing.md **Coming Soon** — Testing tooling and staging environments are in development. Testing support will let you test your edge functions at every level — unit, integration, and end-to-end. Since Edge Compute runs real containers, standard testing tools (pytest, Jest, go test, JUnit) work out of the box. ## Related Resources - [Quick Start](/docs/edge-compute/quickstart) — Deploy your first function - [Local Development](/docs/edge-compute/development) — Run functions locally --- ## To Deploy ### Edge Compute function configuration > Source: https://developers.telnyx.com/docs/edge-compute/configuration.md Function configuration is managed through a `func.toml` file, which defines your project settings, bindings, and deployment options. The `telnyx-edge` CLI is the command-line tool used to develop, test, and deploy functions. For more information on the CLI, refer to [CLI Reference](/docs/edge-compute/reference/cli). - [Bindings](/docs/edge-compute/runtime/bindings) - [Cron Triggers](/docs/edge-compute/configuration/cron-triggers) - [Environment Variables](/docs/edge-compute/configuration/environment-variables) - [Routes & Domains](/docs/edge-compute/configuration/routing) - [Secrets](/docs/edge-compute/configuration/secrets) - [Versions & Deployments](/docs/edge-compute/configuration/versions) --- ### Environment Variables > Source: https://developers.telnyx.com/docs/edge-compute/configuration/environment-variables.md Environment variables allow you to configure your Edge Compute functions without modifying code. They're ideal for non-sensitive configuration values like API endpoints, feature flags, and performance settings that you want to manage alongside your function code. ## Overview Environment variables in Edge Compute are: - **Declared in configuration** — Defined in your function's `func.toml` - **Injected at deployment** — Available when your function starts - **Function-scoped** — Specific to each function - **Version controlled** — Part of your function's configuration ## Defining Environment Variables Define environment variables in your `func.toml` under the `[env_vars]` section: ```toml [edge_compute] func_id = "your-function-id" func_name = "my-function" [env_vars] SERVICE_NAME = "data-processor" VERSION = "1.0.0" LOG_LEVEL = "info" DEBUG = "false" MAX_FILE_SIZE = "10485760" CACHE_TTL = "3600" API_BASE_URL = "https://api.example.com" ``` All values are stored as strings. Parse them to the appropriate type in your code. ## Accessing Environment Variables ```python import os class Function: def __init__(self): # Access environment variables self.service_name = os.getenv("SERVICE_NAME", "unknown-service") self.api_url = os.environ.get("API_BASE_URL") self.debug = os.environ.get("DEBUG", "false").lower() == "true" self.cache_ttl = int(os.environ.get("CACHE_TTL", "300")) self.max_file_size = int(os.getenv("MAX_FILE_SIZE", "10485760")) async def handler(self, request): return { "service": self.service_name, "api_configured": bool(self.api_url), "cache_ttl": self.cache_ttl, "debug": self.debug } ``` ```go package function import ( "encoding/json" "net/http" "os" "strconv" ) func Handle(w http.ResponseWriter, r *http.Request) { // Access environment variables apiURL := os.Getenv("API_BASE_URL") serviceName := os.Getenv("SERVICE_NAME") debug, _ := strconv.ParseBool(os.Getenv("DEBUG")) cacheTTL, _ := strconv.Atoi(os.Getenv("CACHE_TTL")) // Provide defaults for optional values if cacheTTL == 0 { cacheTTL = 300 } response := map[string]interface{}{ "service": serviceName, "api_configured": apiURL != "", "cache_ttl": cacheTTL, "debug": debug, } json.NewEncoder(w).Encode(response) } ``` ```java import java.util.Map; import java.util.Optional; public class ConfigHandler { private final String serviceName; private final String apiUrl; private final boolean debug; private final int cacheTtl; public ConfigHandler() { this.serviceName = getEnv("SERVICE_NAME", "unknown-service"); this.apiUrl = System.getenv("API_BASE_URL"); this.debug = Boolean.parseBoolean(getEnv("DEBUG", "false")); this.cacheTtl = Integer.parseInt(getEnv("CACHE_TTL", "300")); } private String getEnv(String key, String defaultValue) { String value = System.getenv(key); return value != null ? value : defaultValue; } public Map getConfig() { return Map.of( "service", serviceName, "api_configured", apiUrl != null, "cache_ttl", cacheTtl, "debug", debug ); } } ``` ## Environment Variables vs Secrets Use environment variables for non-sensitive configuration. For sensitive data like API keys and passwords, use [Secrets](/docs/edge-compute/configuration/secrets) instead. | Feature | Environment Variables | Secrets | |---------|----------------------|---------| | **Storage** | Plain text in `func.toml` | Encrypted in Telnyx infrastructure | | **Scope** | Function-specific | Organization-wide | | **Version Control** | ✅ Yes (in git) | ❌ No (separate secure storage) | | **Use Case** | Configuration, feature flags | API keys, passwords, tokens | ### When to Use Each Perfect for non-sensitive configuration: - **Application settings** — `LOG_LEVEL`, `DEBUG_MODE`, `PORT` - **Feature flags** — `ENABLE_CACHING`, `MAINTENANCE_MODE` - **Performance tuning** — `MAX_FILE_SIZE`, `BATCH_SIZE`, `TIMEOUT` - **Public endpoints** — `API_BASE_URL`, `CDN_URL`, `WEBHOOK_URL` - **Environment identifiers** — `ENVIRONMENT`, `VERSION`, `SERVICE_NAME` Use secrets for sensitive data: - **API keys** — `STRIPE_API_KEY`, `TWILIO_API_KEY` - **Database credentials** — `DATABASE_PASSWORD`, `REDIS_PASSWORD` - **Authentication tokens** — `JWT_SECRET`, `OAUTH_TOKEN` - **Encryption keys** — `ENCRYPTION_KEY`, `SIGNING_KEY` ## CLI Management Manage environment variables by editing your `func.toml` file directly, then redeploy: ```bash # Edit func.toml to add/update env_vars vim func.toml # Deploy function with updated environment variables telnyx-edge ship ``` Environment variable changes take effect on the next deployment. ## Best Practices ### Naming Conventions Use `UPPER_SNAKE_CASE` for consistency with standard environment variable conventions: ```toml [env_vars] # ✅ Good: Descriptive and consistent SERVICE_NAME = "data-processor" MAX_FILE_SIZE = "10485760" DATABASE_TIMEOUT = "30" ENABLE_CACHING = "true" # ❌ Avoid: Vague or inconsistent naming name = "processor" maxsize = "10485760" ``` ### Type Safety Store all values as strings and parse them in your code: ```toml [env_vars] MAX_CONNECTIONS = "100" TIMEOUT_SECONDS = "30" ENABLE_FEATURE = "true" ``` ### Validation and Defaults Always validate environment variables and provide sensible defaults: ```python import os def get_config(): return { 'max_file_size': int(os.getenv('MAX_FILE_SIZE', '10485760')), 'batch_size': max(1, int(os.getenv('BATCH_SIZE', '100'))), 'timeout': max(1, int(os.getenv('TIMEOUT', '30'))), 'debug': os.getenv('DEBUG', 'false').lower() == 'true', } ``` ```go package function import ( "os" "strconv" ) type Config struct { MaxFileSize int BatchSize int Timeout int Debug bool } func getConfig() Config { return Config{ MaxFileSize: getEnvInt("MAX_FILE_SIZE", 10485760), BatchSize: maxInt(1, getEnvInt("BATCH_SIZE", 100)), Timeout: maxInt(1, getEnvInt("TIMEOUT", 30)), Debug: os.Getenv("DEBUG") == "true", } } func getEnvInt(key string, defaultVal int) int { if val, err := strconv.Atoi(os.Getenv(key)); err == nil { return val } return defaultVal } func maxInt(a, b int) int { if a > b { return a } return b } ``` ```java public class Config { private final int maxFileSize; private final int batchSize; private final int timeout; private final boolean debug; public Config() { this.maxFileSize = getEnvInt("MAX_FILE_SIZE", 10485760); this.batchSize = Math.max(1, getEnvInt("BATCH_SIZE", 100)); this.timeout = Math.max(1, getEnvInt("TIMEOUT", 30)); this.debug = "true".equalsIgnoreCase(System.getenv("DEBUG")); } private int getEnvInt(String key, int defaultVal) { String value = System.getenv(key); if (value == null) return defaultVal; try { return Integer.parseInt(value); } catch (NumberFormatException e) { return defaultVal; } } } ``` ### Environment-Specific Values Use descriptive variable names and document expected values: ```toml [env_vars] # Environment identification ENVIRONMENT = "production" # development, staging, production LOG_LEVEL = "warn" # debug, info, warn, error # Feature flags DEBUG_MODE = "false" METRICS_ENABLED = "true" ``` ## Common Patterns ### Feature Flags ```toml [env_vars] ENABLE_NEW_PROCESSING = "true" ENABLE_BULK_OPERATIONS = "false" ENABLE_ADVANCED_LOGGING = "true" BETA_FEATURES_ENABLED = "false" ``` ### Service Configuration ```toml [env_vars] SERVICE_NAME = "user-auth-service" SERVICE_VERSION = "2.1.0" ENVIRONMENT = "production" API_BASE_URL = "https://api.telnyx.com" CDN_BASE_URL = "https://cdn.telnyx.com" ``` ### Performance Tuning ```toml [env_vars] MAX_CONNECTIONS = "100" CONNECTION_TIMEOUT = "30" READ_TIMEOUT = "60" BATCH_SIZE = "50" MAX_RETRIES = "3" RETRY_DELAY_MS = "1000" ``` ## Debugging Print environment variables for debugging during development: ```python import os def debug_env_vars(): """Print all environment variables for debugging""" print("=== Environment Variables ===") for key in ['SERVICE_NAME', 'API_BASE_URL', 'DEBUG', 'LOG_LEVEL']: value = os.getenv(key, '(not set)') print(f"{key} = {value}") ``` ```go func debugEnvVars() { keys := []string{"SERVICE_NAME", "API_BASE_URL", "DEBUG", "LOG_LEVEL"} fmt.Println("=== Environment Variables ===") for _, key := range keys { value := os.Getenv(key) if value == "" { value = "(not set)" } fmt.Printf("%s = %s\n", key, value) } } ``` ```java public void debugEnvVars() { String[] keys = {"SERVICE_NAME", "API_BASE_URL", "DEBUG", "LOG_LEVEL"}; System.out.println("=== Environment Variables ==="); for (String key : keys) { String value = System.getenv(key); if (value == null) { value = "(not set)"; } System.out.println(key + " = " + value); } } ``` Remove debug logging before deploying to production to avoid exposing configuration details. ## Limits Environment variables are subject to the following constraints: - Variable names must contain only letters, numbers, and underscores - Variable names are case-sensitive - All values are stored and retrieved as strings For specific size limits on variable names and values, refer to the [Edge Compute limits documentation](/docs/edge-compute/reference/limits). ## Related Resources - [Secrets](/docs/edge-compute/configuration/secrets) — Secure storage for sensitive configuration - [Configuration Overview](/docs/edge-compute/configuration) — Complete configuration guide - [Quickstart](/docs/edge-compute/quickstart) — Deploy your first function --- ### Edge Compute secrets management > Source: https://developers.telnyx.com/docs/edge-compute/configuration/secrets.md Secrets are encrypted key-value pairs for storing sensitive configuration data like API keys, database passwords, and authentication tokens. ## Background Secrets provide secure storage that is: - **Encrypted at rest** — Stored with AES-256 encryption - **Encrypted in transit** — All API calls use HTTPS/TLS - **Injected at runtime** — Available as environment variables in your functions - **Organization-scoped** — Shared across all functions in your organization - **Never displayed** — Secret values are never shown in CLI output or logs ## Adding Secrets Use the CLI to store sensitive data: ```bash # Add a secret telnyx-edge secrets add DATABASE_PASSWORD "super-secret-password" # Add multiple secrets telnyx-edge secrets add PAYMENT_API_KEY "sk_live_abc123" telnyx-edge secrets add JWT_SECRET "your-jwt-signing-key" ``` ## Listing Secrets ```bash $ telnyx-edge secrets list SECRET ID SECRET NAME CREATED AT ------------ ---------------- ----------------- 1f4cafea-21ce-4e2b-9740-17d971c3d892 DATABASE_PASSWORD Nov 13, 2025, 21:17 21ef7449-edbb-4248-b869-3d1352563a64 PAYMENT_API_KEY Nov 13, 2025, 01:02 a209b1b3-062c-46f6-a2f2-3b0061751190 JWT_SECRET Nov 13, 2025, 01:01 ``` Secret values are never displayed for security. ## Updating Secrets Use the same `add` command to update an existing secret: ```bash # Update with new value telnyx-edge secrets add DATABASE_PASSWORD "new-password-456" ✅ Secret 'DATABASE_PASSWORD' updated successfully ``` ## Deleting Secrets ```bash telnyx-edge secrets delete OLD_API_KEY ✅ Secret 'OLD_API_KEY' deleted successfully ``` --- ## Accessing Secrets in Your Code Secrets are injected as environment variables when your function runs. ```javascript async function handler(request) { // Secrets are available as environment variables const dbPassword = process.env.DATABASE_PASSWORD; const apiKey = process.env.PAYMENT_API_KEY; if (!dbPassword) { return { error: "DATABASE_PASSWORD not configured" }; } // Use secrets to connect to services const db = await connectDatabase({ password: dbPassword }); return { status: "connected" }; } ``` ```go package main import ( "os" "database/sql" "fmt" ) func handler(w http.ResponseWriter, r *http.Request) { // Secrets are available as environment variables dbPassword := os.Getenv("DATABASE_PASSWORD") apiKey := os.Getenv("PAYMENT_API_KEY") if dbPassword == "" { http.Error(w, "DATABASE_PASSWORD not configured", 500) return } // Use secrets to connect to services db, err := sql.Open("postgres", fmt.Sprintf( "host=db.example.com password=%s", dbPassword, )) json.NewEncoder(w).Encode(map[string]string{"status": "connected"}) } ``` ```python import os def handler(request): # Secrets are available as environment variables db_password = os.environ.get("DATABASE_PASSWORD") api_key = os.environ.get("PAYMENT_API_KEY") if not db_password: return {"error": "DATABASE_PASSWORD not configured"} # Use secrets to connect to services db = connect_database(password=db_password) return {"status": "connected"} ``` ```java import javax.ws.rs.*; import javax.ws.rs.core.*; @Path("/") public class SecureResource { @GET @Produces(MediaType.APPLICATION_JSON) public Response connect() { // Secrets are available as environment variables String dbPassword = System.getenv("DATABASE_PASSWORD"); String apiKey = System.getenv("PAYMENT_API_KEY"); if (dbPassword == null) { return Response.status(500) .entity("{\"error\": \"DATABASE_PASSWORD not configured\"}") .build(); } // Use secrets to connect to services Database db = connectDatabase(dbPassword); return Response.ok("{\"status\": \"connected\"}").build(); } } ``` --- ## Secrets vs Environment Variables ### Use Secrets 🔒 (sensitive data) - **Database credentials** — `DATABASE_URL`, `DATABASE_PASSWORD` - **External API keys** — `PAYMENT_API_KEY`, `EMAIL_SERVICE_KEY` - **Authentication tokens** — `JWT_SECRET`, `OAUTH_SECRET`, `WEBHOOK_SECRET` - **Encryption keys** — `ENCRYPTION_KEY`, `COOKIE_SECRET` ### Use Environment Variables 🌍 (non-sensitive config) - **Application settings** — `LOG_LEVEL`, `MAX_CONNECTIONS`, `TIMEOUT_MS` - **Feature flags** — `ENABLE_CACHING`, `DEBUG_MODE` - **Public endpoints** — `API_BASE_URL`, `CDN_URL` See [Environment Variables](/docs/edge-compute/configuration/environment-variables) for non-sensitive configuration. --- ## Secret Rotation Rotate secrets regularly for security compliance: ```bash # 1. Update the secret with new value telnyx-edge secrets add DATABASE_PASSWORD "new-secure-password" # 2. Redeploy functions to pick up the new value telnyx-edge ship # 3. Verify the function works with new credentials curl https://your-function.telnyxcompute.com/health ``` **When to rotate:** - Monthly for security compliance - After suspected credential compromise - After team member offboarding - When third-party services rotate their keys --- ## Naming Conventions Use clear, descriptive names: | ✅ Good | ❌ Avoid | |---------|---------| | `DATABASE_PASSWORD` | `PASSWORD` | | `STRIPE_API_KEY` | `API_KEY` | | `JWT_SIGNING_SECRET` | `SECRET` | | `SENDGRID_API_KEY` | `EMAIL_KEY` | **Best practices:** - Use `UPPER_SNAKE_CASE` - Include service name: `STRIPE_API_KEY`, `TWILIO_AUTH_TOKEN` - Use standard suffixes: `_KEY`, `_SECRET`, `_TOKEN`, `_PASSWORD` --- ## Local Development 🔜 **Coming soon** — Local development with secrets (`.env` file support) is planned for a future release. For now, set environment variables manually when testing locally: ```bash # Linux/macOS export DATABASE_PASSWORD="local-test-password" python your_function.py # Or inline DATABASE_PASSWORD="test" python your_function.py ``` --- ## Per-Environment Secrets 🔜 **Coming soon** — Per-environment secrets (dev/staging/prod) are planned for a future release. For now, use naming conventions to separate environments: ```bash # Development telnyx-edge secrets add DEV_DATABASE_PASSWORD "dev-password" # Production telnyx-edge secrets add PROD_DATABASE_PASSWORD "prod-password" ``` Then reference the appropriate variable in your code based on an environment flag. --- ## Troubleshooting ### Secret not found in function ```bash # Check that the secret exists telnyx-edge secrets list # If missing, add it telnyx-edge secrets add MY_SECRET "value" # Redeploy the function telnyx-edge ship ``` ### Secret value seems wrong ```bash # Update the secret telnyx-edge secrets add MY_SECRET "correct-value" # Redeploy to pick up new value telnyx-edge ship ``` ### Permission denied ```bash # Check authentication telnyx-edge auth status # Re-authenticate if needed telnyx-edge auth login ``` --- ### Bindings > Source: https://developers.telnyx.com/docs/edge-compute/runtime/bindings.md Bindings allow your edge functions to interact with Telnyx platform services. They provide secure, auto-authenticated access without managing API keys in your code. ## Available Bindings | Binding | Description | Status | |---------|-------------|--------| | [Voice](/docs/voice/programmable-voice) | Make and receive calls | ✅ Available | | [Messaging](/docs/messaging) | Send SMS/MMS | ✅ Available | | [Phone Numbers](/docs/numbers/phone-numbers) | Manage numbers | ✅ Available | | [Fax](/docs/programmable-fax) | Send faxes | ✅ Available | | [Verify](/docs/identity/verify) | 2FA/verification | ✅ Available | | [Cloud Storage](/docs/cloud-storage) | S3-compatible storage | ✅ Available | | KV | Key-value store | 🔜 Coming soon | | SQL DB | Serverless database | 🔜 Coming soon | ## What is a Binding? When you configure a binding for your function, you grant it the capability to access Telnyx services. The binding handles authentication automatically — your API key is never exposed in your code or logs. ``` Your Function → Binding Proxy → Telnyx API ↓ ↓ ↓ Placeholder Resolves Actual API API Key Credentials Call Made ``` When you deploy a function with an active binding: 1. Your function receives a JWT credential as the `TELNYX_API_KEY` environment variable 2. Your function receives the binding proxy URL as `TELNYX_BASE_URL` 3. API calls made to `${TELNYX_BASE_URL}/` are routed through the binding proxy, which authenticates with your real API key 4. The API call proceeds with proper authentication **Important:** You must construct API URLs using `TELNYX_BASE_URL` — do **not** call `https://api.telnyx.com` directly. Direct calls will return 401 because the binding-injected JWT is not a standard Telnyx API key. ```javascript // ✅ Correct — use TELNYX_BASE_URL from binding const baseUrl = process.env.TELNYX_BASE_URL; const response = await fetch(`${baseUrl}/messages`, { headers: { 'Authorization': `Bearer ${process.env.TELNYX_API_KEY}` } }); // ❌ Wrong — direct calls to api.telnyx.com return 401 with binding JWT const response = await fetch('https://api.telnyx.com/v2/messages', { headers: { 'Authorization': `Bearer ${process.env.TELNYX_API_KEY}` } }); ``` **Benefits:** - API key never appears in function code - API key never appears in logs - Credentials can be rotated without code changes ## Creating a Binding One-time setup per organization: ```bash # Create a binding for your organization telnyx-edge bindings create # Verify the binding works telnyx-edge bindings validate ``` ## Managing Bindings ```bash # View your current binding telnyx-edge bindings get # Validate the binding is working telnyx-edge bindings validate # Rotate binding credentials (recommended monthly) telnyx-edge bindings update # Remove the binding telnyx-edge bindings delete ``` --- ## Using Bindings: Telnyx API With a binding configured, use `TELNYX_BASE_URL` and `TELNYX_API_KEY` to make API calls through the binding proxy. The proxy handles authentication with your real API key. ```javascript const http = require('http'); const BASE_URL = process.env.TELNYX_BASE_URL; const API_KEY = process.env.TELNYX_API_KEY; http.createServer(async (req, res) => { if (req.url === '/health/liveness' || req.url === '/health/readiness') { res.writeHead(200, { 'Content-Type': 'text/plain' }); return res.end('ok'); } // Send a message via binding proxy const response = await fetch(`${BASE_URL}/messages`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ from: '+12345678901', to: '+12345678902', text: 'Hello from Edge Compute!' }) }); const data = await response.json(); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(data)); }).listen(8080); ``` ```go package main import ( "fmt" "github.com/telnyx/telnyx-go" ) func main() { // SDK automatically uses TELNYX_API_KEY and TELNYX_BASE_URL client := telnyx.NewClient() // Get account balance balance, err := client.Balance.Get() if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Printf("Balance: %s %s\n", balance.Balance, balance.Currency) } func sendMessage(to, from, text string) error { client := telnyx.NewClient() message, err := client.Messages.Create(&telnyx.MessageParams{ From: from, To: to, Text: text, }) if err != nil { return err } fmt.Printf("Message sent: %s\n", message.ID) return nil } ``` ```python import telnyx # SDK automatically uses TELNYX_API_KEY and TELNYX_BASE_URL # No manual configuration needed! def get_balance(): """Retrieve account balance.""" balance = telnyx.Balance.retrieve() return { "balance": balance.balance, "currency": balance.currency } def send_message(to_number, from_number, text): """Send an SMS using Telnyx SDK.""" message = telnyx.Message.create( from_=from_number, to=to_number, text=text ) return {"message_id": message.id, "status": message.status} def make_call(to_number, from_number, webhook_url): """Initiate an outbound call.""" call = telnyx.Call.create( to=to_number, from_=from_number, connection_id="your-connection-id", webhook_url=webhook_url ) return {"call_id": call.id} ``` ```java import com.telnyx.sdk.ApiClient; import com.telnyx.sdk.api.MessagesApi; import com.telnyx.sdk.model.CreateMessageRequest; @ApplicationScoped public class MessageResource { // SDK automatically uses TELNYX_API_KEY and TELNYX_BASE_URL @Inject ApiClient telnyxClient; @POST @Path("/send") @Produces(MediaType.APPLICATION_JSON) public Response sendMessage(MessageRequest req) { MessagesApi api = new MessagesApi(telnyxClient); CreateMessageRequest request = new CreateMessageRequest() .from(req.getFrom()) .to(req.getTo()) .text(req.getText()); var message = api.createMessage(request); return Response.ok(message).build(); } } ``` --- ## Using Bindings: Cloud Storage **The binding-injected `TELNYX_API_KEY` (a JWT) does not work for S3-compatible Cloud Storage operations.** The Cloud Storage endpoint (`*.telnyxcloudstorage.com`) requires AWS SigV4 authentication with a valid AWS access key — the binding JWT is not a valid AWS credential and will be rejected. For Cloud Storage operations (ListObjectsV2, GetObject, PutObject, etc.), store a regular Telnyx API key as a secret and use it as the S3 access key: ```bash telnyx-edge secrets add TELNYX_STORAGE_KEY YOUR_API_KEY ``` The binding proxy handles `api.telnyx.com` routes only. S3-compatible calls go directly to the storage endpoint. ### Using Cloud Storage with a Secret ```go package main import ( "os" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" ) func getStorageClient() *s3.S3 { // Use a real API key stored as a secret, NOT the binding TELNYX_API_KEY apiKey := os.Getenv("TELNYX_STORAGE_KEY") // set via: telnyx-edge secrets add if apiKey == "" { apiKey = os.Getenv("TELNYX_API_KEY") // fallback: only works with real API key, not binding JWT } sess := session.Must(session.NewSession(&aws.Config{ Endpoint: aws.String("https://us-east-1.telnyxcloudstorage.com"), Region: aws.String("us-east-1"), Credentials: credentials.NewStaticCredentials( apiKey, "", // Not required for Telnyx "", ), })) return s3.New(sess) } func listObjects(bucket string) ([]string, error) { client := getStorageClient() result, err := client.ListObjectsV2(&s3.ListObjectsV2Input{ Bucket: aws.String(bucket), }) if err != nil { return nil, err } var keys []string for _, obj := range result.Contents { keys = append(keys, *obj.Key) } return keys, nil } ``` ```python import os import boto3 def get_storage_client(): """Create a Cloud Storage client using a stored API key secret.""" # Use a real API key stored as a secret, NOT the binding TELNYX_API_KEY api_key = os.environ.get('TELNYX_STORAGE_KEY') # set via: telnyx-edge secrets add if not api_key: api_key = os.environ.get('TELNYX_API_KEY') # fallback: only works with real API key, not binding JWT return boto3.client( 's3', aws_access_key_id=api_key, aws_secret_access_key='', # Not required for Telnyx endpoint_url='https://us-east-1.telnyxcloudstorage.com' ) def upload_file(bucket, key, data): """Upload a file to Cloud Storage.""" client = get_storage_client() client.put_object(Bucket=bucket, Key=key, Body=data) return {"uploaded": key} def download_file(bucket, key): """Download a file from Cloud Storage.""" client = get_storage_client() response = client.get_object(Bucket=bucket, Key=key) return response['Body'].read() def list_objects(bucket, prefix=''): """List objects in a bucket.""" client = get_storage_client() response = client.list_objects_v2(Bucket=bucket, Prefix=prefix) return [obj['Key'] for obj in response.get('Contents', [])] ``` The binding proxy handles `api.telnyx.com` routes (Voice, Messaging, etc.) only. For S3-compatible operations against the Cloud Storage endpoint, use a regular Telnyx API key stored via `telnyx-edge secrets add`. --- ## Using Bindings: KV 🔜 **Coming soon** — KV bindings will allow key-value storage directly from edge functions. --- ## Using Bindings: SQL DB 🔜 **Coming soon** — SQL DB bindings will provide serverless database access from edge functions. --- ## Bindings vs Secrets | Feature | Bindings | Secrets | |---------|----------|---------| | **Purpose** | Telnyx service access | General sensitive data | | **Scope** | One per organization | Unlimited per organization | | **Credentials** | Auto-managed (JWT via proxy) | User-provided | | **Injection** | `TELNYX_API_KEY` (JWT), `TELNYX_BASE_URL` (proxy URL) | Custom environment variables | | **Rotation** | `bindings update` | Manual re-add | | **Use case** | Telnyx SDK/API integration | Database passwords, external API keys | **Use bindings** for Telnyx service access — credentials are managed automatically. **Use [secrets](/docs/edge-compute/configuration/secrets)** for third-party services or when you need multiple different credentials. --- ## Error Handling ```python import telnyx from telnyx.error import TelnyxError, AuthenticationError, APIError def safe_api_call(): try: balance = telnyx.Balance.retrieve() return {"balance": balance.balance} except AuthenticationError as e: # Binding issue - credentials invalid or expired return { "error": "Authentication failed", "hint": "Try: telnyx-edge bindings update" } except APIError as e: # API-level error return { "error": f"API error: {e.message}", "code": e.code } except TelnyxError as e: # General Telnyx error return {"error": str(e)} ``` --- ## Troubleshooting ### Binding not found ```bash $ telnyx-edge bindings get ❌ No binding found # Solution: Create a binding $ telnyx-edge bindings create ``` ### Validation failed ```bash $ telnyx-edge bindings validate ❌ Binding validation failed # Solutions: # 1. Update the binding telnyx-edge bindings update # 2. Check your Telnyx account status # 3. Verify authentication telnyx-edge auth status ``` ### Function can't access Telnyx API ```bash # Check binding exists telnyx-edge bindings get # Validate binding works telnyx-edge bindings validate # Redeploy function to get latest credentials telnyx-edge ship ``` --- ### Routes & Domains > Source: https://developers.telnyx.com/docs/edge-compute/configuration/routing.md After deploying a function with `telnyx-edge ship`, your function is automatically assigned a public URL that makes it accessible from the internet. ## Public URL Patterns Every deployed function receives a unique public URL based on your function name and organization: ``` https://{funcName}-{orgId}.telnyxcompute.com ``` For development environments, URLs use the `.dev` subdomain: ``` https://{funcName}-{orgId}.dev.telnyxcompute.com ``` ### URL Components | Component | Description | |-----------|-------------| | `funcName` | The function name specified in your `func.toml` | | `orgId` | Your Telnyx organization identifier | ### Example If your function is named `hello-world` and your organization ID is `abc123`, your production URL would be: ``` https://hello-world-abc123.telnyxcompute.com ``` ## Accessing Your Functions Once deployed, your function can handle HTTP requests at its public URL: ```bash # GET request curl https://my-function-abc123.telnyxcompute.com # POST request with JSON body curl -X POST \ -H "Content-Type: application/json" \ -d '{"name": "test"}' \ https://my-function-abc123.telnyxcompute.com ``` The `telnyx-edge ship` command displays your function's live URL after successful deployment: ```bash 📡 Your function is live at: https://my-function-abc123.telnyxcompute.com 💡 Test your function: curl https://my-function-abc123.telnyxcompute.com ``` ## Custom Domains **Coming soon** — Custom domain support is planned for a future release. This will allow you to map your own domains (e.g., `api.yourdomain.com`) to your Edge Compute functions. ## Region Placement **Coming soon** — Region placement and pinning support is planned for a future release. This will allow you to specify geographic regions where your functions should run for latency optimization or data residency requirements. --- ### Cron Triggers > Source: https://developers.telnyx.com/docs/edge-compute/configuration/cron-triggers.md **Coming Soon** — Cron triggers are planned for a future release. Cron triggers will let you schedule your edge functions to run automatically at specified intervals. ## Current Alternative Until cron triggers are available, use an external scheduler to call your function's HTTP endpoint: ```yaml # .github/workflows/cron.yml name: Scheduled Function Call on: schedule: - cron: '0 * * * *' # Every hour jobs: trigger: runs-on: ubuntu-latest steps: - run: curl -X POST https://my-func-org123.telnyxcompute.com/cron ``` ## Related - [Environment Variables](/docs/edge-compute/configuration/environment-variables) — Configure your functions - [Secrets](/docs/edge-compute/configuration/secrets) — Store sensitive configuration --- ### Versions & Deployments > Source: https://developers.telnyx.com/docs/edge-compute/configuration/versions.md **Coming Soon** — Version history and rollback commands are planned for a future release. Version management will let you track deployments, roll back to previous versions, and implement gradual rollout strategies. ## Current Deployment Model Today, `telnyx-edge ship` deploys your function as an atomic update. Each deployment fully replaces the previous version. ## Current Rollback Strategy Until rollback commands are available, use Git: ```bash # Revert to previous commit git revert HEAD telnyx-edge ship ``` Or checkout a specific version: ```bash git log --oneline git checkout abc123 . telnyx-edge ship ``` ## Related - [Quick Start](/docs/edge-compute/quickstart) — Deploy your first function - [CI/CD](/docs/edge-compute/deploy) — Deployment pipelines --- ### CI/CD > Source: https://developers.telnyx.com/docs/edge-compute/deploy.md Automate your Edge Compute deployments with continuous integration and delivery pipelines. This guide covers integration with popular CI/CD platforms. ## Overview The deployment workflow: 1. **Push code** to your repository 2. **CI runs tests** and builds your function 3. **Deploy to Telnyx** using the CLI 4. **Verify deployment** with health checks ## Authentication CI/CD pipelines need a Telnyx API key to deploy functions. Create a deployment key in the [Telnyx Portal](https://portal.telnyx.com) with Edge Compute permissions. Store your API key as a secret in your CI/CD platform. Never commit API keys to your repository. ## GitHub Actions Deploy on every push to `main`: ```yaml # .github/workflows/deploy.yml name: Deploy Edge Function on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Telnyx CLI run: | # Download from https://github.com/team-telnyx/edge-compute/releases wget -qO- https://github.com/team-telnyx/edge-compute/releases/latest/download/telnyx-edge-linux-amd64.tar.gz | tar xz sudo mv telnyx-edge /usr/local/bin/ echo "$HOME/.telnyx/bin" >> $GITHUB_PATH - name: Deploy function env: TELNYX_API_KEY: ${{ secrets.TELNYX_API_KEY }} run: telnyx-edge ship ``` ### With Tests Run tests before deploying: ```yaml name: Test and Deploy on: push: branches: [main] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install dependencies run: pip install -r requirements.txt - name: Run tests run: pytest tests/ deploy: needs: test if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Telnyx CLI run: | # Download from https://github.com/team-telnyx/edge-compute/releases wget -qO- https://github.com/team-telnyx/edge-compute/releases/latest/download/telnyx-edge-linux-amd64.tar.gz | tar xz sudo mv telnyx-edge /usr/local/bin/ echo "$HOME/.telnyx/bin" >> $GITHUB_PATH - name: Deploy env: TELNYX_API_KEY: ${{ secrets.TELNYX_API_KEY }} run: telnyx-edge ship ``` ### Multi-Language Examples ```yaml jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci - name: Run tests run: npm test - name: Install Telnyx CLI run: | # Download from https://github.com/team-telnyx/edge-compute/releases wget -qO- https://github.com/team-telnyx/edge-compute/releases/latest/download/telnyx-edge-linux-amd64.tar.gz | tar xz sudo mv telnyx-edge /usr/local/bin/ echo "$HOME/.telnyx/bin" >> $GITHUB_PATH - name: Deploy env: TELNYX_API_KEY: ${{ secrets.TELNYX_API_KEY }} run: telnyx-edge ship ``` ```yaml jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 with: go-version: '1.22' - name: Run tests run: go test ./... - name: Install Telnyx CLI run: | # Download from https://github.com/team-telnyx/edge-compute/releases wget -qO- https://github.com/team-telnyx/edge-compute/releases/latest/download/telnyx-edge-linux-amd64.tar.gz | tar xz sudo mv telnyx-edge /usr/local/bin/ echo "$HOME/.telnyx/bin" >> $GITHUB_PATH - name: Deploy env: TELNYX_API_KEY: ${{ secrets.TELNYX_API_KEY }} run: telnyx-edge ship ``` ```yaml jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install dependencies run: pip install -r requirements.txt - name: Run tests run: pytest - name: Install Telnyx CLI run: | # Download from https://github.com/team-telnyx/edge-compute/releases wget -qO- https://github.com/team-telnyx/edge-compute/releases/latest/download/telnyx-edge-linux-amd64.tar.gz | tar xz sudo mv telnyx-edge /usr/local/bin/ echo "$HOME/.telnyx/bin" >> $GITHUB_PATH - name: Deploy env: TELNYX_API_KEY: ${{ secrets.TELNYX_API_KEY }} run: telnyx-edge ship ``` ```yaml jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Java uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: '17' cache: 'maven' - name: Build and test run: mvn verify - name: Install Telnyx CLI run: | # Download from https://github.com/team-telnyx/edge-compute/releases wget -qO- https://github.com/team-telnyx/edge-compute/releases/latest/download/telnyx-edge-linux-amd64.tar.gz | tar xz sudo mv telnyx-edge /usr/local/bin/ echo "$HOME/.telnyx/bin" >> $GITHUB_PATH - name: Deploy env: TELNYX_API_KEY: ${{ secrets.TELNYX_API_KEY }} run: telnyx-edge ship ``` ## GitLab CI ```yaml # .gitlab-ci.yml stages: - test - deploy test: stage: test image: python:3.11 script: - pip install -r requirements.txt - pytest tests/ deploy: stage: deploy image: ubuntu:latest only: - main script: - # Download from https://github.com/team-telnyx/edge-compute/releases wget -qO- https://github.com/team-telnyx/edge-compute/releases/latest/download/telnyx-edge-linux-amd64.tar.gz | tar xz sudo mv telnyx-edge /usr/local/bin/ - export PATH="$HOME/.telnyx/bin:$PATH" - telnyx-edge ship variables: TELNYX_API_KEY: $TELNYX_API_KEY ``` ## CircleCI ```yaml # .circleci/config.yml version: 2.1 jobs: test: docker: - image: cimg/python:3.11 steps: - checkout - run: name: Install dependencies command: pip install -r requirements.txt - run: name: Run tests command: pytest deploy: docker: - image: cimg/base:current steps: - checkout - run: name: Install Telnyx CLI command: | # Download from https://github.com/team-telnyx/edge-compute/releases wget -qO- https://github.com/team-telnyx/edge-compute/releases/latest/download/telnyx-edge-linux-amd64.tar.gz | tar xz sudo mv telnyx-edge /usr/local/bin/ echo 'export PATH="$HOME/.telnyx/bin:$PATH"' >> $BASH_ENV - run: name: Deploy command: telnyx-edge ship workflows: test-and-deploy: jobs: - test - deploy: requires: - test filters: branches: only: main ``` ## Environment Promotion Deploy to different environments (staging, production) using branches or tags: ```yaml # .github/workflows/deploy.yml name: Deploy on: push: branches: [main, staging] tags: ['v*'] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Telnyx CLI run: | # Download from https://github.com/team-telnyx/edge-compute/releases wget -qO- https://github.com/team-telnyx/edge-compute/releases/latest/download/telnyx-edge-linux-amd64.tar.gz | tar xz sudo mv telnyx-edge /usr/local/bin/ echo "$HOME/.telnyx/bin" >> $GITHUB_PATH - name: Deploy to dev if: github.ref == 'refs/heads/staging' env: TELNYX_API_KEY: ${{ secrets.TELNYX_API_KEY_DEV }} run: telnyx-edge ship --env dev - name: Deploy to production if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') env: TELNYX_API_KEY: ${{ secrets.TELNYX_API_KEY_PRODUCTION }} run: telnyx-edge ship --env prod ``` ## Rollbacks To roll back a deployment, redeploy a previous Git commit: ```bash # Revert to previous commit git revert HEAD git push origin main # Or checkout and deploy a specific commit git checkout telnyx-edge ship git checkout main ``` Version history and CLI rollback commands are coming soon. For now, use Git-based rollbacks as shown above. ### Health Checks Add health checks to detect issues after deployment: ```yaml - name: Deploy run: telnyx-edge ship - name: Health check run: | sleep 10 # Wait for deployment to propagate STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://my-function-abc123.telnyxcompute.com/health) if [ "$STATUS" != "200" ]; then echo "Health check failed!" exit 1 fi ``` If the health check fails, manually revert and redeploy: ```bash git revert HEAD git push origin main # Triggers new deployment ``` ## Secrets Management ### GitHub Actions Secrets 1. Go to **Settings → Secrets and variables → Actions** 2. Click **New repository secret** 3. Add `TELNYX_API_KEY` with your API key ### GitLab CI Variables 1. Go to **Settings → CI/CD → Variables** 2. Add `TELNYX_API_KEY` as a masked variable ### Environment-Specific Secrets Use different API keys per environment: ```yaml env: TELNYX_API_KEY: ${{ github.ref == 'refs/heads/main' && secrets.TELNYX_API_KEY_PROD || secrets.TELNYX_API_KEY_STAGING }} ``` ## Deployment Notifications ### Slack Notification ```yaml - name: Notify Slack if: always() uses: slackapi/slack-github-action@v1 with: payload: | { "text": "Deployment ${{ job.status }}: ${{ github.repository }}", "blocks": [ { "type": "section", "text": { "type": "mrkdwn", "text": "*${{ github.repository }}* deployment ${{ job.status }}\nCommit: `${{ github.sha }}`\nBranch: `${{ github.ref_name }}`" } } ] } env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} ``` ## Best Practices ### Branch Protection Require CI to pass before merging: - Enable required status checks on `main` branch - Require pull request reviews - Run tests on all pull requests ### Deployment Frequency - **Development**: Deploy on every commit to feature branches - **Staging**: Deploy on merge to `staging` branch - **Production**: Deploy on merge to `main` or version tags ### Monitoring Deployments After deployment, monitor: - Function invocation metrics - Error rates - Response latency - Cold start frequency See [Observability](/docs/edge-compute/observability) for monitoring setup. ## Troubleshooting ### Authentication Errors ``` Error: Invalid API key ``` - Verify `TELNYX_API_KEY` secret is set correctly - Check API key has Edge Compute permissions - Ensure secret name matches workflow variable ### Build Failures ``` Error: Build failed ``` - Check your `func.toml` configuration is valid - Verify dependencies are correctly specified - Check dependency versions match CI environment - Verify `func.toml` configuration ### Deployment Timeouts ``` Error: Deployment timed out ``` - Check function size is within [limits](/docs/edge-compute/reference/limits) - Verify network connectivity from CI runner - Retry with `--verbose` flag for details ## Related Resources - [CLI Reference](/docs/edge-compute/reference/cli) — Full CLI documentation - [Local Development](/docs/edge-compute/development) — Test locally before CI - [Observability](/docs/edge-compute/observability) — Monitor deployments --- ## To Monitor ### Edge Compute observability > Source: https://developers.telnyx.com/docs/edge-compute/observability.md **Coming Soon** — Observability features (logs, metrics, tracing) are in development. Observability tools will let you monitor your Edge Compute functions with real-time logs, performance metrics, and request tracing. ## Related Resources - [Limits](/docs/edge-compute/reference/limits) — Resource limits and quotas - [Best Practices](/docs/edge-compute/best-practices) — Optimization tips --- ## Reference ### CLI Reference > Source: https://developers.telnyx.com/docs/edge-compute/reference/cli.md The `telnyx-edge` CLI is the primary tool for deploying and managing Edge Compute functions. ## Installation ```bash # macOS (Homebrew) brew tap telnyx/tap brew install telnyx-edge # Linux / macOS (direct download) # Download from https://github.com/team-telnyx/edge-compute/releases wget -qO- https://github.com/team-telnyx/edge-compute/releases/latest/download/telnyx-edge-linux-amd64.tar.gz | tar xz sudo mv telnyx-edge /usr/local/bin/ # Verify installation telnyx-edge --help ``` ## Authentication ### Login with OAuth ```bash telnyx-edge auth login ``` Opens a browser window for OAuth authentication. After successful login, credentials are stored locally. ### API Key Authentication ```bash # Set API key directly telnyx-edge auth api-key set YOUR_API_KEY # Clear stored API key telnyx-edge auth api-key clear ``` ### Check Authentication Status ```bash telnyx-edge auth status ``` Returns your authenticated organization and user info. ### Logout ```bash telnyx-edge auth logout ``` Clears stored credentials. --- ## Function Management ### Create a New Function ```bash telnyx-edge new-func -l= -n= ``` | Flag | Description | Options | |------|-------------|---------| | `-l`, `--language` | Function language | `python`, `go`, `quarkus` | | `-n`, `--name` | Function name | Alphanumeric, hyphens allowed | **Examples:** ```bash # Create a Python function telnyx-edge new-func -l=python -n=my-webhook # Create a Go function telnyx-edge new-func -l=go -n=call-handler # Create a Java (Quarkus) function telnyx-edge new-func -l=quarkus -n=enterprise-app ``` This creates a new directory with: - `func.toml` — Function configuration - Starter code in the chosen language - `.gitignore` with sensible defaults ### Deploy a Function ```bash telnyx-edge ship ``` Deploys the function from the current directory. Reads configuration from `func.toml`. **Options:** | Flag | Description | |------|-------------| | `--from-dir` | Deploy from a specific directory | | `--timeout` | Deployment timeout in seconds | **Examples:** ```bash # Deploy from current directory cd my-function telnyx-edge ship # Deploy from a specific directory telnyx-edge ship --from-dir=./my-function ``` After deployment, the CLI outputs your function URL: ``` ✓ Function deployed successfully URL: https://my-webhook-abc123.telnyxcompute.com ``` ### Delete a Function ```bash telnyx-edge delete-func ``` This permanently deletes the function and all its versions. This action cannot be undone. **Example:** ```bash telnyx-edge delete-func my-old-function ``` ### List Functions ```bash telnyx-edge list ``` Lists all functions in your organization with their status and URLs. --- ## Secrets Management Secrets are encrypted environment variables that are injected at runtime. ### Add a Secret ```bash telnyx-edge secrets add "" ``` **Examples:** ```bash # Add a single secret telnyx-edge secrets add DATABASE_URL "postgres://user:pass@host:5432/db" # Add an API key telnyx-edge secrets add OPENAI_API_KEY "sk-..." ``` Secrets are available as environment variables in your function: ```python import os db_url = os.environ.get('DATABASE_URL') ``` ### List Secrets ```bash telnyx-edge secrets list ``` Lists secret names (values are not displayed for security). ### Delete a Secret ```bash telnyx-edge secrets delete ``` **Example:** ```bash telnyx-edge secrets delete OLD_API_KEY ``` --- ## Bindings Bindings provide automatic authentication to Telnyx services. ### Create a Binding ```bash telnyx-edge bindings create ``` Creates a binding for your organization. This is a one-time setup that enables automatic Telnyx API key injection. ### View Current Binding ```bash telnyx-edge bindings get ``` Shows binding configuration and status. ### Validate Binding ```bash telnyx-edge bindings validate ``` Tests that the binding is working correctly by making a test API call. ### Regenerate Binding Tokens ```bash telnyx-edge bindings update ``` Regenerates binding tokens. Use this if you suspect credentials have been compromised. ### Delete Binding ```bash telnyx-edge bindings delete ``` Removes the binding. Functions will no longer have automatic Telnyx API access. --- ## System Commands ### Check CLI Status ```bash telnyx-edge status ``` Shows CLI version, authentication status, and connectivity to Edge Compute services. ### Get Help ```bash # General help telnyx-edge --help # Command-specific help telnyx-edge new-func --help telnyx-edge ship --help ``` ### Update CLI ```bash # macOS (Homebrew) brew upgrade telnyx-edge # Linux / direct install # Download from https://github.com/team-telnyx/edge-compute/releases wget -qO- https://github.com/team-telnyx/edge-compute/releases/latest/download/telnyx-edge-linux-amd64.tar.gz | tar xz sudo mv telnyx-edge /usr/local/bin/ ``` --- ## Configuration File Each function has a `func.toml` configuration file: ```toml [edge_compute] func_name = "my-webhook" language = "python" [env_vars] LOG_LEVEL = "info" API_ENDPOINT = "https://api.example.com" [build] # Optional build settings entry_point = "main.py" ``` ### Configuration Fields | Field | Description | Required | |-------|-------------|----------| | `func_name` | Function name (used in URL) | Yes | | `language` | Runtime language | Yes | | `[env_vars]` | Environment variables | No | | `[build]` | Build configuration | No | See [Environment Variables](/docs/edge-compute/configuration/environment-variables) for more configuration options. --- ## Common Workflows ### First Deployment ```bash # 1. Login telnyx-edge auth login # 2. Create function telnyx-edge new-func -l=python -n=my-first-function # 3. Edit code cd my-first-function # ... edit your code ... # 4. Deploy telnyx-edge ship ``` ### Adding Secrets ```bash # 1. Add secrets telnyx-edge secrets add DATABASE_URL "postgres://..." telnyx-edge secrets add API_KEY "secret-key" # 2. Redeploy to pick up secrets telnyx-edge ship ``` ### Setting Up Bindings ```bash # 1. Create binding (one-time) telnyx-edge bindings create # 2. Validate it works telnyx-edge bindings validate # 3. Deploy function that uses Telnyx SDK telnyx-edge ship ``` --- ## Troubleshooting ### "Not authenticated" Error ```bash # Re-authenticate telnyx-edge auth logout telnyx-edge auth login ``` ### Deployment Fails ```bash # Check function configuration cat func.toml # Verify you're in the right directory ls -la # Check CLI status telnyx-edge status ``` ### Function Not Responding ```bash # Check function exists telnyx-edge list # Redeploy telnyx-edge ship ``` ## Next Steps - [Quickstart](/docs/edge-compute/quickstart) — Deploy your first function - [Secrets](/docs/edge-compute/configuration/secrets) — Secure credential management - [Bindings](/docs/edge-compute/runtime/bindings) — Access Telnyx services --- ### Edge Compute architecture > Source: https://developers.telnyx.com/docs/edge-compute/reference/architecture.md Edge Compute runs your code in real Linux containers on Telnyx's global edge network. Unlike V8 isolate-based platforms, you get a full POSIX environment with native language runtimes. ## Overview ``` ┌─────────────────────────────────────────────────────────────┐ │ Your Request │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Edge Location │ │ (nearest to your users) │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ Ingress (TLS termination, routing) │ │ │ └──────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ KNative Serving │ │ │ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │ │ │ │ Container │ │ Container │ │ Container │ │ │ │ │ │ (your fn) │ │ (your fn) │ │ (your fn) │ │ │ │ │ └────────────┘ └────────────┘ └────────────┘ │ │ │ │ ▲ ▲ ▲ │ │ │ │ └───────────────┴───────────────┘ │ │ │ │ Auto-scaled based on load │ │ │ └──────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ## Containers vs Isolates Edge Compute uses **real containers**, not V8 isolates. This gives you: | Feature | V8 Isolates (Cloudflare) | Containers (Telnyx) | |---------|--------------------------|---------------------| | Languages | JavaScript/WASM only | Python, Go, Node.js, Java | | Runtime | Reimplemented web APIs | Native runtimes, full stdlib | | File system | None | Full POSIX filesystem | | Cold start | ~5ms | ~100-500ms | | Dependencies | Limited, bundled | Any native packages | | Process model | Single-threaded | Multi-process capable | **When to choose containers:** - Need native packages (ML libraries, image processing, etc.) - Porting existing server code - Require language-specific features not available in V8 - Need filesystem access ## Request Lifecycle When a request arrives at your function: ### 1. Routing The request hits the nearest edge location. TLS is terminated, and the request is routed to your function based on the URL: ``` https://my-function-abc123.telnyxcompute.com/api/data └──────────┬──────────┘ Routes to your function ``` ### 2. Container Selection The platform selects a container to handle the request: - **Warm container available** → Request handled immediately (~1-5ms routing overhead) - **No warm container** → Cold start: new container initialized (~100-500ms) ### 3. Execution Your function code runs with: - Full network access (outbound HTTP, TCP, UDP) - Environment variables and secrets injected - [Bindings](/docs/edge-compute/runtime/bindings) for Telnyx services ### 4. Response The response is returned to the caller. The container stays warm for subsequent requests. ## Cold Starts A cold start occurs when a new container must be initialized: ``` Cold Start Timeline ├── Container image pull (cached at edge) ~0ms (cached) ├── Runtime initialization (Python/Go/Node) ~50-200ms ├── Global code execution (imports, setup) ~50-300ms └── First request handled varies ``` **Minimizing cold starts:** 1. **Keep containers warm** — Consistent traffic keeps containers alive 2. **Lazy initialization** — Defer expensive setup until needed 3. **Minimize dependencies** — Smaller images = faster starts 4. **Use connection pools** — Initialize once, reuse across requests See [Execution Model](/docs/edge-compute/runtime/execution-model) for optimization patterns. ## Edge Network Functions run on Telnyx's edge infrastructure across multiple locations: | Region | Location | Status | |--------|----------|--------| | North America | Atlanta | ✅ Live | | North America | New York | ✅ Live | | North America | Los Angeles | ✅ Live | | Europe | Frankfurt | ✅ Live | | Asia Pacific | Sydney | ✅ Live | Requests are automatically routed to the nearest available location for lowest latency. ## Scaling Edge Compute automatically scales based on traffic: ``` Traffic Pattern Platform Response ───────────────────── ───────────────────── Low traffic → Fewer containers (may scale to zero) Traffic spike → New containers start (cold starts) Sustained load → Containers stay warm Traffic drops → Containers gradually recycle ``` Each container handles **one request at a time**. The platform scales horizontally by adding containers as needed. ## Security ### Isolation Each function runs in its own isolated container: - **Network isolation** — Functions cannot communicate with each other directly - **Filesystem isolation** — No shared filesystem between functions - **Process isolation** — Separate process namespace ### Credentials API credentials are never exposed in your code: - [Bindings](/docs/edge-compute/runtime/bindings) inject credentials securely - [Secrets](/docs/edge-compute/configuration/secrets) are encrypted at rest - Credentials don't appear in logs ## Internal Architecture For those curious about the internals: | Component | Purpose | |-----------|---------| | ECG (Edge Compute Gateway) | API gateway, function management, routing | | KNative | Serverless runtime, auto-scaling, revision management | | Kourier | Ingress controller, TLS termination | | Meter Reader | Usage metering for billing | Functions are deployed as KNative services on Kubernetes clusters at each edge location. ## Next Steps - [Execution Model](/docs/edge-compute/runtime/execution-model) — Deep dive into function lifecycle - [Limits](/docs/edge-compute/reference/limits) — Resource constraints - [Bindings](/docs/edge-compute/runtime/bindings) — Connect to Telnyx services --- ### Edge Compute limits and quotas > Source: https://developers.telnyx.com/docs/edge-compute/reference/limits.md Edge Compute enforces resource limits to ensure fair usage and platform stability. ## Execution Limits | Limit | Default | Maximum | |-------|---------|---------| | Request timeout | 30 seconds | 60 seconds | | CPU time per request | 30 seconds | 60 seconds | | Memory per container | 256 MB | 512 MB | | Request body size | 10 MB | 10 MB | | Response body size | 10 MB | 10 MB | ### Request Timeout Functions must return a response within the timeout period. If exceeded, the request is terminated and returns a 504 Gateway Timeout. ```python # Handle long operations gracefully import signal def handler(request): # Set internal timeout shorter than platform limit signal.alarm(25) # 25 seconds, leaving buffer try: result = long_running_operation() signal.alarm(0) # Cancel alarm return result except TimeoutError: return {"error": "Operation timed out"} ``` ### Memory Each container has a fixed memory allocation. If your function exceeds memory limits, the container is terminated and a new one starts for subsequent requests. **Tips for memory efficiency:** - Stream large files instead of loading into memory - Use generators for large datasets - Clean up resources after use - Avoid global caches that grow unbounded ```python # Bad — loads entire file into memory def bad_handler(request): data = large_file.read() # May exceed memory return process(data) # Good — streams the file def good_handler(request): for chunk in stream_file(): yield process_chunk(chunk) ``` --- ## Function Limits | Limit | Value | |-------|-------| | Function code size | 50 MB (compressed) | | Environment variables per function | 64 | | Environment variable name size | 256 bytes | | Environment variable value size | 5 KB | | Secrets per organization | 100 | | Secret value size | 10 KB | ### Code Size The 50 MB limit includes your code and all dependencies (after compression). Most functions are well under this limit. **If you're hitting size limits:** - Remove unused dependencies - Use lighter alternatives (e.g., `httpx` instead of `requests` + `urllib3`) - Exclude development dependencies from production builds - Consider splitting into multiple functions --- ## Network Limits | Limit | Value | |-------|-------| | Outbound connections per request | 100 | | Outbound request timeout | 30 seconds (configurable) | | DNS resolution timeout | 5 seconds | ### Outbound Connections Each invocation can make up to 100 outbound network connections. This includes: - HTTP/HTTPS requests - Database connections - TCP sockets **Connection pooling recommended:** ```python # Initialize connection pool globally (once per container) import httpx client = httpx.Client( timeout=10.0, limits=httpx.Limits(max_connections=20) ) def handler(request): # Reuse the pooled connection response = client.get('https://api.example.com/data') return response.json() ``` --- ## Rate Limits | Limit | Value | |-------|-------| | Deployments per hour | 60 | | API requests per minute | 1,000 | | Concurrent function invocations | No hard limit (auto-scales) | ### Deployment Limits You can deploy up to 60 times per hour per organization. This is rarely hit in normal development. ### Invocation Scaling There's no hard limit on concurrent invocations — the platform auto-scales to handle traffic. However, each new container incurs a cold start, so extremely spiky traffic may see increased latency. --- ## Account Limits | Limit | Value | |-------|-------| | Functions per organization | 100 | | Total function invocations | Based on plan | | Total CPU time | Based on plan | Need higher limits? Contact [support@telnyx.com](mailto:support@telnyx.com) to discuss enterprise plans. --- ## Storage Limits (Coming Soon) These limits apply to upcoming storage features: ### KV Storage | Limit | Value | |-------|-------| | Key size | 512 bytes | | Value size | 25 MB | | Keys per namespace | 1 billion | | Namespaces per organization | 100 | | Read operations per second | 10,000 | | Write operations per second | 1,000 | ### SQL Database | Limit | Value | |-------|-------| | Database size | 10 GB | | Databases per organization | 10 | | Rows per table | No hard limit | | Query timeout | 30 seconds | --- ## Error Handling When limits are exceeded, the platform returns specific error codes: | Error | Code | Meaning | |-------|------|---------| | Request Timeout | 504 | Function didn't respond in time | | Memory Exceeded | 500 | Container terminated due to memory | | Payload Too Large | 413 | Request/response body exceeded limit | | Rate Limited | 429 | Too many requests or deployments | **Example error response:** ```json { "error": { "code": "timeout_exceeded", "message": "Function execution exceeded 30 second timeout", "request_id": "req_abc123" } } ``` --- ## Best Practices ### Stay Within Limits 1. **Set conservative timeouts** — Use 25 seconds internally when the platform limit is 30 2. **Monitor memory** — Log memory usage during development 3. **Stream large data** — Avoid buffering entire files 4. **Use connection pools** — Reuse HTTP/database connections ### Handle Limit Errors ```python import httpx def handler(request): try: # Set explicit timeout shorter than platform limit response = httpx.get( 'https://api.example.com/data', timeout=25.0 ) return response.json() except httpx.TimeoutException: return { "error": "Upstream API timed out", "status": 504 } except httpx.HTTPError as e: return { "error": f"Request failed: {e}", "status": 502 } ``` ## Next Steps - [Execution Model](/docs/edge-compute/runtime/execution-model) — Optimize cold starts and performance - [Architecture](/docs/edge-compute/reference/architecture) — Understand the platform - [Configuration](/docs/edge-compute/configuration) — Set environment and timeouts ---