Voice AI

How to Build an Edge Cache Invalidation Agent with the Telnyx Agent SDK

Trigger cache invalidation across edge locations via webhook, mark caches dirty with KV, update a shared manifest in Cloud Storage, and notify ops via SMS. All orchestrated by the Agent SDK on Telnyx Edge Compute with zero-credential messaging.

The Multi-Region Cache Problem

When you run a content-heavy application across multiple edge locations, cache invalidation becomes a critical operational concern. A CMS publishes a new page version. A deployment pushes updated static assets. An emergency requires immediate content removal. In each case, every edge location needs to know that its cached copy is stale.

The standard approach involves calling a CDN purge API, then running a separate notification service to alert the ops team that the purge happened. Two services, two API keys, two integration points, and a gap between the purge and the notification where nobody knows whether the invalidation actually worked.

The Edge Cache Invalidation Agent does all of it on one network. Telnyx Edge Compute hosts the webhook and orchestrates the pipeline. Telnyx KV stores per-location dirty flags. Telnyx Cloud Storage holds the shared invalidation manifest. Telnyx Messaging delivers the SMS notification. One API key for KV and Cloud Storage. Zero credentials in code for messaging. One deploy command.

What It Does

You send a webhook to an Edge Compute endpoint. The pipeline runs three stages in sequence:

  1. Invalidate — Mark the cache as dirty for each edge location via KV writes.
  2. Update Manifest — Append the invalidation event to a shared manifest in Cloud Storage.
  3. Notify — Send an SMS to the ops team with a summary of what changed and where.

Each stage is a queued job on a stateful actor. State survives across stages, so the list of invalidated locations from stage 1 is available to stage 2 for the manifest entry, and the summary from stage 2 is available to stage 3 for the SMS text. You can check the status of any pipeline run via a REST endpoint and get back the full state, including which locations were invalidated, whether the manifest was updated, and whether the SMS was sent.

StageActionAPI
TriggerReceive webhook, validate input, create actorPOST /invalidate
StartStore metadata, queue invalidateCacheAgent.start()
1. InvalidateKV.put per location with dirty flag and TTLCACHE_KV.put("cache:{location}:{contentId}", value, { expirationTtl: 3600 })
2. Update ManifestCloud Storage read/append/write shared manifestCACHE_STORAGE.put("cache-manifest.json", updated)
3. NotifySMS summary to ops teamthis.env.TELNYX.messages.send()
StatusCheck pipeline progressGET /status/:agentId
Cache StatusCheck if a location's cache is dirtyGET /cache-status/:location/:contentId

The Architecture

The application is a TypeScript Agent SDK project deployed to Telnyx Edge Compute. It uses three Telnyx products: Edge Compute (hosting and actor runtime), KV (per-location cache flags), Cloud Storage (shared manifest), and Messaging (SMS notifications).

  Content update webhook (POST /invalidate)
        │
        ▼
  ┌──────────────────────────────────────────┐
  │ CacheAgent.start()                        │
  │  → this.queue("invalidate")               │
  │  → this.queue("updateManifest")           │
  │  → this.queue("notify")                   │
  └────────┬─────────────────────────────────┘
           │
           ▼
  Stage 1: invalidate()
    → KV.put("cache:{location}:{contentId}", { dirty: true, version })
    → for each edge location
           │
           ▼
  Stage 2: updateManifest()
    → CloudStorage.get("cache-manifest.json")
    → append invalidation entry
    → CloudStorage.put("cache-manifest.json", updated)
           │
           ▼
  Stage 3: notify()
    → this.env.TELNYX.messages.send({ from, to, text })
    → SMS: "Cache invalidated: {contentId} v{version} — N locations updated"

The webhook handler receives a JSON payload with a content ID, content version, and array of edge locations. It validates the input, generates a Dapr-safe actor name, creates a CacheAgent actor instance, and calls start(). The agent stores the parameters in durable state and queues the first pipeline stage.

The Agent SDK runtime handles the rest. Each queued stage runs as a separate invocation of the actor, with state automatically loaded from the previous invocation. If a stage fails, the error is captured in state and the pipeline stops. You can inspect the status at any time.

Zero-Credential Messaging

The most important design decision in this pipeline is the [telnyx] binding in telnyx.toml:

[telnyx]
binding = "TELNYX"

This single line gives the Agent SDK runtime a pre-authenticated Telnyx client. At runtime, the actor accesses it through this.env.TELNYX. The SMS call in the notify() stage needs no API key, no Authorization header, no environment variable:

await this.env.TELNYX.messages.send({
  from: state.senderPhone,
  to: state.alertPhone,
  text: smsText,
});

Only TELNYX_API_KEY is needed as a secret, and only for KV and Cloud Storage, which use the API key directly. Messaging is zero-credential via the binding. Rotate keys in the portal and the binding picks up the new credentials automatically. No code changes. No redeploy.

Durable State Across Pipeline Stages

The Agent SDK provides durable state through this.setState() and this.getState(). State is scoped to the actor instance and survives across queued invocations:

// In CacheAgent.start()
await this.setState({
  contentId: params.contentId,
  contentVersion: params.contentVersion,
  locations: params.locations,
  senderPhone: this.env.SENDER_PHONE,
  alertPhone: this.env.ALERT_PHONE,
  status: "invalidating",
  createdAt: Date.now(),
});
this.queue("invalidate");

// In invalidate()
const state = await this.getState();
// ... mark caches dirty ...
await this.setState({
  invalidatedLocations: invalidated,
  status: "updating_manifest",
});
this.queue("updateManifest");

// In updateManifest()
const state = await this.getState();
// ... update manifest ...
await this.setState({
  manifestUpdated: true,
  status: "notifying",
});
this.queue("notify");

State is not held in memory between stages. It is persisted to the actor store and reloaded on the next invocation. This means the pipeline is resilient to runtime restarts, scaling events, or delays between stages. If the notify() stage fails (for example, the SMS gateway is temporarily down), the agent retries and reads the summary from state. It doesn't re-invalidate caches or re-update the manifest.

KV-Based Cache Invalidation

Each edge location gets a KV key with the pattern cache:{location}:{contentId}. The value is a JSON object with dirty: true, the new contentVersion, and a timestamp:

const kvKey = `cache:${location}:${state.contentId}`;
const value = JSON.stringify({
  dirty: true,
  contentVersion: state.contentVersion,
  invalidatedAt: Date.now(),
});
await this.env.CACHE_KV.put(kvKey, value, { expirationTtl: 3600 });

KV keys expire after 1 hour (TTL). If a location hasn't refreshed its cache by then, the flag disappears and the location serves stale content until the next invalidation. Adjust the TTL based on your cache refresh window.

You can check whether a specific location's cache is dirty at any time:

curl https://edge-cache-invalidation-agent-<id>.telnyxcompute.com/cache-status/us-east-1/blog/how-to-build-x

Response:

{
  "location": "us-east-1",
  "contentId": "/blog/how-to-build-x",
  "dirty": true,
  "contentVersion": "2026-08-19-v2"
}

Clear the dirty flag to simulate a cache refresh:

curl -X POST https://edge-cache-invalidation-agent-<id>.telnyxcompute.com/cache-clear/us-east-1/blog/how-to-build-x

Cloud Storage Manifest

The shared manifest (cache-manifest.json) in Cloud Storage is an append-only log of invalidation events. Each entry records what changed, the new version, which locations were invalidated, and when:

const manifestEntry = {
  contentId: state.contentId,
  contentVersion: state.contentVersion,
  invalidatedLocations: state.invalidatedLocations,
  updatedAt: Date.now(),
};

manifest.entries.push(manifestEntry);
await this.env.CACHE_STORAGE.put(MANIFEST_KEY, JSON.stringify(manifest, null, 2), {
  contentType: "application/json",
});

This gives ops a durable audit trail across all invalidations. Production should rotate or partition the manifest by date to avoid unbounded growth.

The Pipeline Queue Primitives

The Agent SDK uses this.queue() to schedule non-blocking work. Each call adds a stage to the actor's internal queue and returns immediately. The runtime invokes the queued method when capacity is available:

this.queue("invalidate");
this.queue("updateManifest");
this.queue("notify");

These three calls do not run in parallel. They run in the order queued, with state persisting between each. If invalidate() fails, updateManifest() never runs. If updateManifest() succeeds but notify() fails, the manifest update is still saved in state and you can retry the notify stage manually or inspect the error.

The queue is durable. If the Edge Compute worker restarts between stages, the queue is restored and processing resumes from the next pending stage.

Setup

Prerequisites

- API key (Portal → API Keys) - A phone number with SMS enabled (Portal → Numbers) - KV namespace (Portal → Storage → KV) - Cloud Storage bucket (Portal → Storage → Buckets)

1. Clone and install

git clone https://github.com/team-telnyx/telnyx-code-examples.git
cd telnyx-code-examples/edge-cache-invalidation-agent
npm install

2. Set secrets

telnyx-edge secret set TELNYX_API_KEY KEY0123456789ABCDEF
telnyx-edge secret set ALERT_PHONE +18005551234
telnyx-edge secret set SENDER_PHONE +18005551234

TELNYX_API_KEY is your Telnyx API v2 key from the Portal. ALERT_PHONE is the ops number that receives SMS notifications. SENDER_PHONE is a Telnyx number with SMS enabled.

3. Update telnyx.toml

Replace the placeholder values:

[storage.kv.CACHE_KV]
id = "your-kv-namespace-uuid"

[storage.cloudstorage.CACHE_STORAGE]
bucket_name = "your-bucket-name"
region = "us-central-1"

4. Deploy

telnyx-edge ship

The ship command builds the TypeScript project, uploads the bundle, and prints a URL like edge-cache-invalidation-agent-<id>.telnyxcompute.com.

5. Test

Health check:

curl https://edge-cache-invalidation-agent-<id>.telnyxcompute.com/health/liveness

Trigger an invalidation:

curl -X POST https://edge-cache-invalidation-agent-<id>.telnyxcompute.com/invalidate \
  -H "Content-Type: application/json" \
  -d '{
    "content_id": "/blog/how-to-build-x",
    "content_version": "2026-08-19-v2",
    "locations": ["us-east-1", "us-west-1", "eu-central-1", "ap-southeast-1"]
  }'

Check pipeline status:

curl https://edge-cache-invalidation-agent-<id>.telnyxcompute.com/status/<agentId>

API Reference

POST /invalidate

Trigger a cache invalidation across edge locations.

Request body:

{
  "content_id": "/blog/how-to-build-x",
  "content_version": "2026-08-19-v2",
  "locations": ["us-east-1", "us-west-1", "eu-central-1", "ap-southeast-1"]
}

Response (200):

{
  "action": "queued",
  "agentId": "bloghowtobuildx-20260819v2-1724080800000",
  "contentId": "/blog/how-to-build-x",
  "contentVersion": "2026-08-19-v2",
  "locations": ["us-east-1", "us-west-1", "eu-central-1", "ap-southeast-1"],
  "statusUrl": "/status/bloghowtobuildx-20260819v2-1724080800000"
}

GET /status/:agentId

Check the status of an invalidation pipeline.

Response:

{
  "contentId": "/blog/how-to-build-x",
  "contentVersion": "2026-08-19-v2",
  "status": "done",
  "invalidatedLocations": ["us-east-1", "us-west-1", "eu-central-1", "ap-southeast-1"],
  "manifestUpdated": true,
  "smsSent": true,
  "createdAt": 1724080800000,
  "completedAt": 1724080805000
}

GET /cache-status/:location/:contentId

Check if a specific location's cache is dirty for a content ID.

Response:

{
  "location": "us-east-1",
  "contentId": "/blog/how-to-build-x",
  "dirty": true,
  "contentVersion": "2026-08-19-v2"
}

POST /cache-clear/:location/:contentId

Clear the dirty flag for a location (simulates cache refresh).

Response:

{
  "action": "cleared",
  "location": "us-east-1",
  "contentId": "/blog/how-to-build-x"
}

GET /locations

List demo edge locations.

GET /health/liveness / GET /health/readiness

Health check endpoints.

Related Examples

Try It Yourself

git clone https://github.com/team-telnyx/telnyx-code-examples.git
cd telnyx-code-examples/edge-cache-invalidation-agent
npm install
telnyx-edge secret set TELNYX_API_KEY YOUR_API_KEY
telnyx-edge secret set ALERT_PHONE +18005551234
telnyx-edge secret set SENDER_PHONE +18005551234
telnyx-edge ship

Then trigger an invalidation and watch the pipeline run.

Telnyx is AI Communications Infrastructure — voice, messaging, SIP, AI, IoT, and Cloud Storage on one private, global network. This sample uses four Telnyx products through a single API key and one deploy command. The Agent SDK's [telnyx] binding means SMS delivery needs zero credentials in code, while KV and Cloud Storage use the same key for per-location cache flags and shared manifest storage.

Frequently Asked Questions

Q: Why does messaging need zero credentials while KV and Cloud Storage need an API key?

The [telnyx] binding in telnyx.toml is a platform-managed authentication channel. Edge Compute injects a pre-authenticated client into this.env.TELNYX at runtime. This client can call Telnyx Messaging because it is part of the same platform identity. KV and Cloud Storage, however, use direct API calls that require an explicit API key. The Telnyx API key serves as that credential for storage operations. One key covers both KV and Cloud Storage. Messaging is zero-credential via the binding.

Q: What happens if a pipeline stage fails?

The error is caught, stored in the actor's state under the error field, and the pipeline stops. The status endpoint returns the error message and the stage that failed. You can inspect the state, fix the issue (for example, set a missing secret), and retry the stage manually if your agent implementation supports it. Because state is durable, a retry reads progress from the previous successful stage instead of re-running it.

Q: Is the pipeline synchronous or asynchronous?

The invalidate endpoint returns immediately with a queued status. The pipeline runs asynchronously in the background. For a typical invalidation across four locations, the full pipeline (invalidate + updateManifest + notify) completes in under a second. You poll the status endpoint or build a webhook callback if you need real-time notification.

Q: Can I invalidate multiple content IDs at once?

The example triggers one invalidation per request. To support batch invalidation, modify the upload handler to accept an array of content IDs and loop over them in the invalidate stage. Each content ID gets its own agent instance, so batching is a matter of orchestration, not architecture.

Q: How do I know if an edge location actually refreshed its cache?

The KV dirty flag is a signal, not a guarantee. The edge location must poll or subscribe to the KV key to know when to refresh. The GET /cache-status/:location/:contentId endpoint lets you check whether the flag is still set. The POST /cache-clear/:location/:contentId endpoint simulates a refresh by clearing the flag. In production, your edge cache layer should read the KV key on each request and refresh when dirty is true.

Q: What is the carrier-edge advantage?

The webhook, KV writes, manifest update, and SMS delivery all happen inside the same Telnyx network boundary. No egress fees, no cross-cloud latency, no third-party CDN API. This is the Physics pillar of Telnyx's three-pillar thesis: carrier-edge compute beats cloud-edge on latency and cost.

Ready to build with low-latency voice AI?

Join developers building the future of real-time conversations