> ## Documentation Index
> Fetch the complete documentation index at: https://developers.telnyx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Actor Alarms

> Schedule deferred work from inside a Stateful Actor method using the single per-actor alarm. At-least-once delivery with automatic redelivery on failure; make your handler idempotent.

Every Stateful Actor has a **single per-instance alarm** — a one-shot timer you can set, replace, read, or clear from inside any method. When the alarm fires, the runtime calls the actor's `alarm(alarmInfo)` handler. Alarms are the way to do deferred work without an external scheduler.

| What                     | How                                                                |
| ------------------------ | ------------------------------------------------------------------ |
| Set or replace the alarm | `await this.ctx.storage.setAlarm(when)` — `when` is ms since epoch |
| Read the scheduled time  | `await this.ctx.storage.getAlarm()` → `number \| null`             |
| Clear the alarm          | `await this.ctx.storage.deleteAlarm()`                             |
| Top-level alias          | `await this.ctx.setAlarm(when)` (mirrors `storage.setAlarm`)       |
| Handle the fire          | override `async alarm(info: AlarmInfo)` on your subclass           |

There is one alarm per actor instance, not per method or per key. Setting it again replaces the previous time.

## Set and Handle

```ts theme={null}
import { StatefulActor, type AlarmInfo } from "@telnyx/edge-runtime";

export class Session extends StatefulActor {
  // Called when a session is touched — push the timeout out by 5 minutes.
  async touch() {
    await this.ctx.storage.put("last_seen", Date.now());
    await this.ctx.storage.setAlarm(Date.now() + 5 * 60_000);
  }

  // Fires once when the alarm goes off.
  async alarm(info: AlarmInfo): Promise<void> {
    const lastSeen = (await this.ctx.storage.get<number>("last_seen")) ?? 0;
    if (Date.now() - lastSeen > 5 * 60_000) {
      // session actually idle — clean up
      await this.ctx.storage.deleteAll();
    }
  }
}
```

The handler is just another method call on the actor — it inherits the same single-threaded dispatch guarantee as any other method. You don't need a lock around the state the alarm touches.

## Delivery Contract

`AlarmInfo`:

```ts theme={null}
interface AlarmInfo {
  retryCount: number;   // 0 on first delivery; increments on each retry
  isRetry: boolean;     // true when retryCount > 0
}
```

* **At-least-once delivery.** Plan for the handler to run more than once for the same scheduled time. Make writes idempotent — re-check state in `storage` before applying effects (as the `Session` example above does), or record a done-marker keyed by the scheduled time.
* **A failed run is redelivered 3 times, about a second apart.** If the handler throws (or exceeds its time budget), the platform re-fires it with `retryCount` incremented — 4 attempts total, then the alarm is **deleted** (`getAlarm()` returns `null`). There is no signal when that happens.
* **A failing handler loses its alarm — don't use `throw` as your retry mechanism.** Three retries a second apart won't outlast a real outage. If the deferred work must happen, make the handler tolerate its own errors: catch, record, and re-arm with `setAlarm` at a backoff you choose.
* **Use `info.isRetry` to branch.** On retry, log it, treat the work as best-effort, or skip already-completed steps.

```ts theme={null}
async alarm(info: AlarmInfo): Promise<void> {
  if (info.isRetry) {
    // redelivery after a failed run — be extra careful about double-applies
  }
  // re-check state in storage before applying effects, then do the work
}
```

## Patterns

### TTL / expiry

Set the alarm when you create the row; on fire, check the row is still expired and delete it. If the row was touched since, reset the alarm instead.

### Retry backoff

If your method kicks off a flaky external call, set an alarm to retry later; on fire, attempt again and either succeed or set a new alarm with a longer backoff.

### Session timeout

See `Session.touch` above — every interaction pushes the alarm out by the idle window; the alarm only fires if no one touches the session in time.

### Coalesced drain

Because there's only one alarm per instance, you can use it as a "flush trigger": set it on the first write, and when it fires, drain the queue and clear it. Subsequent writes during the window just append to the queue.

## Limits

* **One alarm per actor instance.** If you need multiple timers, keep a queue in `storage` and use the single alarm to drive a scheduler loop.
* **`when` is ms since epoch.** Don't pass seconds. Firing precision is about one second — don't schedule sub-second work with it.
* **No recurring flag.** If you want periodic work, set the next alarm at the end of your `alarm` handler.
* **The handler has a time budget.** `alarm()` runs under its own wall-clock cap — larger than the 30-second method budget, on the order of minutes, but still bounded. A run that exceeds it counts as a failure and is redelivered.
* **`deleteAll()` does not clear the alarm.** Keys and the alarm are separate; a pending alarm still fires after `deleteAll()`, and a handler that re-arms itself will keep running against empty state. To decommission an actor: `deleteAlarm()` first, then `deleteAll()`.

## Next Steps

* [Runtime API](/docs/edge-compute/stateful-actors/api-reference) — `ActorStorage`, `ActorContext`, `AlarmInfo`
* [Quick Start](/docs/edge-compute/stateful-actors/quick-start) — Scaffold, deploy, and curl an Account actor
* [Shared Actors](/docs/edge-compute/stateful-actors/shared-actors) — Reach one actor from many functions
