# Telnyx Compute: Functions — Full Documentation > Complete page content for Functions (Compute 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.com/llms/compute/functions.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 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(); } } ``` --- ## Bindings ### Overview > Source: https://developers.telnyx.com/docs/edge-compute/runtime/bindings.md A binding maps a name you declare in `func.toml` to an authenticated resource handle, resolved by the runtime — the credential is injected for you and never appears in your code, bundle, or logs. Each binding resolves on the `env` object (from `@telnyx/edge-runtime`) — `env.MY_TELNYX`, `env.SECRETS`, and so on. The `[telnyx]` API binding and `telnyx-edge types` are **TypeScript-only**. They rely on the `telnyx` Node SDK and a generated `.d.ts`; other runtimes (`js`, `go`, `python`, `quarkus`) don't get a typed `env` handle. ## Every binding works the same way ```toml # func.toml — 1. declare [telnyx] binding = "MY_TELNYX" ``` ```bash # 2. generate types telnyx-edge types ``` ```ts // 3. use it — typed and authenticated import { env } from "@telnyx/edge-runtime"; const { data } = await env.MY_TELNYX.availablePhoneNumbers.list({ filter: { country_code: "US" }, }); ``` The binding name (`MY_TELNYX`) is yours to choose; it becomes the property on `env`. The SDK types `.data` as `T | undefined` for list calls. Under `tsc --strict`, indexing into `data` (e.g. `data.length`) fails with `TS18048: 'data' is possibly 'undefined'`. Coalesce before use: `const arr = list.data ?? [];`. ## Manifest: `func.toml` or `telnyx.toml` Bindings are declared in your project manifest. `telnyx-edge types` reads either form and writes `telnyx-env.d.ts`, typing `env.` for each declared binding. - **`func.toml`** (classic) — the standard `[edge_compute]` project file. Declare `[telnyx]` and `[[secrets]]` bindings in it. - **`telnyx.toml`** (umbrella) — a manifest with top-level `name` and `main` that declares the same `[telnyx]` and `[[secrets]]` bindings. ```toml # telnyx.toml — umbrella manifest name = "my-app" main = "src/index.ts" [telnyx] binding = "MY_TELNYX" [[secrets]] binding = "GREETING" name = "DEMO_GREETING" ``` ## Catalogue | Binding | Declare | For | |---------|---------|-----| | [Telnyx API](/docs/edge-compute/telnyx-api/index) | `[telnyx]` | Voice, Messaging, Numbers — the Telnyx API | | [Secrets](/docs/edge-compute/configuration/secrets) | `[[secrets]]` | Your own credentials and config | | [Key-Value](/docs/edge-compute/kv/index) | `[storage.kv.]` | Low-latency key/value storage | ## Bindings vs secrets - **Binding** — a Telnyx or platform resource, authenticated for you (`env.MY_TELNYX`). - **Secret** — a value you supply (`env.SECRETS.get("STRIPE_KEY")`). Use a binding for platform resources; use a [secret](/docs/edge-compute/configuration/secrets) for your own third-party credentials. --- ### Overview > Source: https://developers.telnyx.com/docs/edge-compute/telnyx-api.md The Telnyx API binding puts a ready-to-use, authenticated Telnyx client on `env`. You never handle an API key — the binding injects credentials at the edge and keeps them out of your code, bundle, and logs. ```ts import { env } from "@telnyx/edge-runtime"; await env.MY_TELNYX.messages.send({ from: "+13125550100", to: "+13125550101", text: "Hello from Edge Compute", }); ``` - **Free-formed name** — `MY_TELNYX` is whatever you set as `binding` in `func.toml`. It becomes the property on `env`. - **Typed** — `telnyx-edge types` types `env.MY_TELNYX` as the Telnyx client. - **One per organization** — every function in the org shares it. Start with the [Quick start](/docs/edge-compute/telnyx-api/quick-start). The org-level credential behind the binding (`bindings create` / `validate` / `update`) is account-level and rarely touched — see the [CLI reference](/docs/edge-compute/reference/cli#bindings). --- ### Quick start > Source: https://developers.telnyx.com/docs/edge-compute/telnyx-api/quick-start.md A complete function that returns your Telnyx account balance — declared, typed, shipped, and called. ## 1. Create a function ```bash telnyx-edge new-func --language ts --name balance-check cd balance-check ``` ## 2. Declare the binding Add a `[telnyx]` block to the generated `func.toml`: ```toml # func.toml [edge_compute] func_id = "60c5ce48-…" # created by new-func func_name = "balance-check" [telnyx] binding = "MY_TELNYX" ``` ## 3. Generate types ```bash telnyx-edge types ``` `env.MY_TELNYX` is now typed as the Telnyx client. ## 4. Write the handler ```ts // index.ts import * as http from "node:http"; import { env } from "@telnyx/edge-runtime"; const port = Number(process.env.PORT ?? 8080); http.createServer(async (req, res) => { // Match /health and any /health/* probe path the platform requires // (the scaffold generates this guard — keep it). if (req.url === "/health" || req.url?.startsWith("/health/")) { return res.writeHead(200).end(); } const { data } = await env.MY_TELNYX.balance.retrieve(); res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify(data)); }).listen(port); ``` ## 5. Ship ```bash telnyx-edge ship ``` ## 6. Call it `ship` prints your function URL. Hit it: ```bash curl https://balance-check-.telnyxcompute.com ``` ```json { "credit_limit": "1000.00", "frozen": "0.00", "currency": "USD", "available_credit": "1100.00", "pending": "0.00", "balance": "100.00", "record_type": "balance" } ``` --- ### API reference > Source: https://developers.telnyx.com/docs/edge-compute/telnyx-api/api-reference.md `env.MY_TELNYX` is a ready-to-use, authenticated Telnyx client handle — call it like any Telnyx API client, with auth already wired in (no `new Telnyx(...)`, no API key to manage). Calls take the shape `env.MY_TELNYX..(...)`, using resource and method names — not raw HTTP paths: - `` — a camelCase property: `messages`, `calls`, `balance`, `availablePhoneNumbers`, … - `` — a method on the resource: `.send`, `.dial`, `.list`, `.retrieve`, … The names don't always track the HTTP API (`messages.send` is `POST /messages`, but `messages.cancelScheduled` is `DELETE /messages/{id}`), so discover them rather than guessing from endpoints: - **Autocomplete** — after [`telnyx-edge types`](/docs/edge-compute/telnyx-api/quick-start), your editor completes `env.MY_TELNYX.` with every resource and method. - **[Client method reference (`api.md`)](https://github.com/team-telnyx/telnyx-node/blob/master/api.md)** — lists every `.(...)` and the endpoint it maps to. - **[HTTP API reference](/api-reference)** — endpoint parameters and behavior. ```ts import { env } from "@telnyx/edge-runtime"; await env.MY_TELNYX.messages.send({ from: "+13125550100", to: "+13125550101", text: "Hello" }); // POST /messages await env.MY_TELNYX.calls.dial({ connection_id: "2985…", from: "+13125550100", to: "+13125550101" }); // POST /calls await env.MY_TELNYX.availablePhoneNumbers.list({ filter: { country_code: "US" } }); // GET /available_phone_numbers await env.MY_TELNYX.balance.retrieve(); // GET /balance ``` Each method returns the API response; list and retrieve calls expose the payload on `.data`. ## Errors Calls reject on API errors. Catch and inspect: ```ts try { await env.MY_TELNYX.messages.send({ from: "+13125550100", to: "+13125550101", text: "Hello" }); } catch (err) { // err.status (e.g. 400), err.message, err.error (parsed body) } ``` --- ### Receiving messages > Source: https://developers.telnyx.com/docs/edge-compute/telnyx-api/receiving-messages.md Inbound SMS is webhook-driven: Telnyx POSTs a `message.received` event to your messaging profile's webhook, and your Edge Compute function is that webhook. Replying to another Telnyx number is **on-net** — no 10DLC campaign required. ## 1. Write the handler ```ts // index.ts import * as http from "node:http"; import { env } from "@telnyx/edge-runtime"; const port = Number(process.env.PORT ?? 8080); const received: any[] = []; // in-memory; use KV or SQL DB for durable storage function body(req: http.IncomingMessage): Promise { return new Promise((r) => { let b = ""; req.on("data", (c) => (b += c)); req.on("end", () => r(b)); }); } http.createServer(async (req, res) => { // GET: see what's been received if (req.method === "GET") { res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify({ received }, null, 2)); return; } // POST: Telnyx inbound webhook — parse it through the binding const evt = env.MY_TELNYX.webhooks.unsafeUnwrap<{ data: any }>(await body(req)).data; if (evt?.event_type === "message.received") { const p = evt.payload; const from = p.from.phone_number; const to = p.to[0].phone_number; received.unshift({ from, to, text: p.text, at: evt.occurred_at }); // Auto-reply on-net (no 10DLC when the recipient is a Telnyx number) await env.MY_TELNYX.messages.send({ from: to, to: from, text: `You said: ${p.text}` }); } res.writeHead(200); res.end(); }).listen(port); ``` Declare the binding in `func.toml` (see the [Quick start](/docs/edge-compute/telnyx-api/quick-start)): ```toml [telnyx] binding = "MY_TELNYX" ``` ## 2. Ship ```bash telnyx-edge ship ``` ## 3. Point a messaging profile at it Set a messaging profile's inbound webhook to your function URL, then assign your number to that profile: ```bash # webhook -> your function curl -X POST https://api.telnyx.com/v2/messaging_profiles \ -H "Authorization: Bearer $TELNYX_API_KEY" -H "Content-Type: application/json" \ -d '{"name":"inbound-demo","webhook_url":"https://YOUR-FUNC.telnyxcompute.com","whitelisted_destinations":["US"]}' # assign your number to the profile (use the profile id from the response) curl -X PATCH https://api.telnyx.com/v2/phone_numbers/YOUR-NUMBER-ID/messaging \ -H "Authorization: Bearer $TELNYX_API_KEY" -H "Content-Type: application/json" \ -d '{"messaging_profile_id":"YOUR-PROFILE-ID"}' ``` ## 4. Test on-net Send from another Telnyx number on your account to your function's number: ```bash curl -X POST https://api.telnyx.com/v2/messages \ -H "Authorization: Bearer $TELNYX_API_KEY" -H "Content-Type: application/json" \ -d '{"from":"+1ANOTHER_TELNYX_NUMBER","to":"+1YOUR_FUNC_NUMBER","text":"hello"}' ``` You get back **"You said: hello"** on-net, and `GET https://YOUR-FUNC.telnyxcompute.com` shows what arrived. The inbound event the function parses looks like: ```json { "data": { "event_type": "message.received", "occurred_at": "2026-06-19T16:06:17.464+00:00", "payload": { "from": { "phone_number": "+1..." }, "to": [ { "phone_number": "+1..." } ], "text": "hello" } } } ``` **Only on-net replies skip 10DLC.** Receiving is always free. Replying to a Telnyx number is on-net (no campaign). Replying to an off-net number — e.g. a personal mobile — is application-to-person traffic and requires 10DLC registration. --- ### Handling calls > Source: https://developers.telnyx.com/docs/edge-compute/telnyx-api/handling-calls.md Inbound voice is webhook-driven through Call Control: Telnyx POSTs call events to your Call Control application's webhook, and your Edge Compute function is that webhook. On `call.initiated` you answer the call; on `call.answered` you play audio. ## 1. Write the handler ```ts // index.ts import * as http from "node:http"; import { env } from "@telnyx/edge-runtime"; const port = Number(process.env.PORT ?? 8080); const AUDIO_URL = "https://YOUR-HOST/song.mp3"; // a reachable HTTPS mp3/wav you have rights to function body(req: http.IncomingMessage): Promise { return new Promise((r) => { let b = ""; req.on("data", (c) => (b += c)); req.on("end", () => r(b)); }); } http.createServer(async (req, res) => { if (req.method === "GET") { res.writeHead(200).end(); return; } // health // parse the inbound webhook through the binding const evt = env.MY_TELNYX.webhooks.unsafeUnwrap<{ data: any }>(await body(req)).data; const id = evt?.payload?.call_control_id; if (evt?.event_type === "call.initiated" && id) { await env.MY_TELNYX.calls.actions.answer(id, {}); } else if (evt?.event_type === "call.answered" && id) { await env.MY_TELNYX.calls.actions.startPlayback(id, { audio_url: AUDIO_URL }); } res.writeHead(200); res.end(); }).listen(port); ``` Declare the binding in `func.toml`: ```toml [telnyx] binding = "MY_TELNYX" ``` ## 2. Ship ```bash telnyx-edge ship ``` ## 3. Point a Call Control app at it ```bash # create a Call Control app whose webhook is your function curl -X POST https://api.telnyx.com/v2/call_control_applications \ -H "Authorization: Bearer $TELNYX_API_KEY" -H "Content-Type: application/json" \ -d '{"application_name":"voice-demo","webhook_event_url":"https://YOUR-FUNC.telnyxcompute.com"}' # route your number to that app (connection_id = the app id from the response) curl -X PATCH https://api.telnyx.com/v2/phone_numbers/YOUR-NUMBER-ID \ -H "Authorization: Bearer $TELNYX_API_KEY" -H "Content-Type: application/json" \ -d '{"connection_id":"YOUR-CALL-CONTROL-APP-ID"}' ``` ## 4. Test Call the number from any phone. The function answers and plays your audio. `audio_url` must be a publicly reachable HTTPS `.mp3` or `.wav`. The flow is two events — `call.initiated` (answer) then `call.answered` (play) — so handle both. To loop, hang up, or chain more actions, respond to later events (`playback.ended`, `call.hangup`) the same way. --- ## 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 --- ### 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 ``` --- ### 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 | `ts`, `js`, `go`, `python`, `quarkus` | | `-n`, `--name` | Function name | Alphanumeric, hyphens allowed | `--language` defaults to `ts` when omitted. **Examples:** ```bash # Create a TypeScript function (default) telnyx-edge new-func -l=ts -n=my-webhook # 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` plus language-specific files: | Language | Scaffolded files | |----------|------------------| | `ts` | `index.ts`, `package.json`, `tsconfig.json` | | `js` | `index.js`, `package.json` | | `go` | `handler.go`, `go.mod` | | `python` | `function`, `pyproject.toml` | | `quarkus` | `src/`, `pom.xml`, `mvnw`, `.mvn`, `README.md`, `.gitignore` | ### 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 monitoring timeout (Go duration: `2m`, `300s`; default `5m0s`) | **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. ### Inspect Revisions ```bash telnyx-edge revisions list ``` Lists the most recent revisions for a function (newest first). Each successful `ship` produces an immutable revision that can be rolled back to with `rollback`. ### Roll Back a Function ```bash telnyx-edge rollback ``` Instantly retargets traffic to an existing, immutable revision across all clusters — there is no rebuild or re-upload. Only revisions that reached `deploy_ok` can be rolled back to. Use `revisions list ` to see available revision ids. --- ## 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 These commands manage the **org-level Telnyx credential** (one per organization) behind the [Telnyx API binding](/docs/edge-compute/telnyx-api/index). They're account-level — the per-function flow needs none of them: declaring `[telnyx]` in `func.toml` wires the binding automatically on `ship`. ### Create a Binding ```bash telnyx-edge bindings create ``` Provisions the org-level Telnyx credential. One per organization. ### 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. ### Generate Binding Types ```bash telnyx-edge types ``` Writes `telnyx-env.d.ts` at the project root so `env.` is type-safe in function code, folding every declared binding into one global `Env` interface. Re-run after changing a binding declaration. The `[telnyx]` binding and `telnyx-edge types` are **TypeScript-only**. `types` generates a `.d.ts` declaration file consumed by `tsc`; it has no effect on `js`, `go`, `python`, or `quarkus` runtimes. --- ## System Commands ### Check CLI Status ```bash telnyx-edge status ``` Shows 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. The `new-func` scaffold generates the `[edge_compute]` block (`func_id`, `func_name`); the runtime infers the language from the files you scaffolded with `-l`. Add a `[telnyx]` block to wire the [Telnyx API binding](/docs/edge-compute/telnyx-api/index): ```toml # func.toml [edge_compute] func_id = "60c5ce48-…" # created by new-func func_name = "my-webhook" [telnyx] binding = "MY_TELNYX" # becomes env.MY_TELNYX after `telnyx-edge types` [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_id` | Function id (created by `new-func`, reused on later ships) | Yes | | `func_name` | Function name (used in URL) | Yes | | `language` | Runtime language | No — inferred from `new-func -l` | | `[telnyx]` | Telnyx API binding declaration (`binding = "NAME"`) | No | | `[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 ``` --- ## 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 ---