telega/dead_letter

Updates that crashed the chat instance handling them.

A handler that panics takes its chat instance down with it. The bot actor already notices (it monitors every instance it dispatched to) and answers the poller or the webhook for the update the instance never finished — but the update itself is gone, and with it any chance of finding out what the bot was asked to do.

A dead-letter queue keeps it. Give the bot a KeyValueStorage with telega.with_dead_letters and every update whose instance crashed is written under the dlq: prefix as the raw JSON Telegram sent, together with the reason. Read them back with telega.dead_letters, re-dispatch them with telega.replay_dead_letters once the bug is fixed, and drop them with [telega.drop_dead_letter].

let storage = storage.new(...)

telega.new(api_client)
|> telega.router(router)
|> telega.with_dead_letters(storage.dead_letters_from_storage(storage, ttl: None))
|> telega.start()

Writing is fire-and-forget in a spawned process: the bot actor is the only dispatcher a bot has, and a wedged storage backend must not be able to stop it. A write that fails is logged and the letter is lost — the queue is a debugging aid, not a durable inbox.

Entries are keyed by update_id, so a replayed update that crashes again overwrites its own entry instead of growing the queue.

Types

One stored update, as read back from the queue.

pub type DeadLetter {
  DeadLetter(key: String, update: types.Update, reason: String)
}

Constructors

  • DeadLetter(key: String, update: types.Update, reason: String)

    Arguments

    key

    Storage key, e.g. "dlq:123456". Pass it to drop_dead_letter.

    update

    The update, ready to be handed back to telega.handle_update.

    reason

    Why the instance went down, as the BEAM reported it.

A place to put updates that crashed, and to read them back from.

Built from a KeyValueStorage with storage.dead_letters_from_storage; the backend’s own error type is flattened to a String here, because the bot that holds one is already generic over three type parameters and the queue is never on a path where the error is matched on.

pub opaque type DeadLetters

Values

pub fn drop(
  letters letters: DeadLetters,
  key key: String,
) -> Result(Nil, String)

Forget one letter.

pub fn key_for(update: types.Update) -> String

The key an update is stored under.

pub fn list(
  letters letters: DeadLetters,
) -> Result(#(List(DeadLetter), List(String)), String)

Every stored letter, oldest update_id first.

A payload that will not decode is reported in the Error list rather than silently skipped — a queue that quietly drops what it cannot read is worse than no queue.

pub fn new(
  put put: fn(String, String) -> Result(Nil, String),
  keys keys: fn() -> Result(List(String), String),
  read read: fn(String) -> Result(option.Option(String), String),
  drop drop: fn(String) -> Result(Nil, String),
) -> DeadLetters

Build a queue out of four storage primitives. Backends live in telega/storage; this is the seam a custom one plugs into.

pub const prefix: String

The key prefix every dead letter is stored under.

pub fn read(
  letters letters: DeadLetters,
  key key: String,
) -> Result(DeadLetter, String)

Read one letter back by its storage key.

pub fn record(
  letters letters: DeadLetters,
  update update: types.Update,
  reason reason: String,
) -> Result(Nil, String)

Store one update. Called by the bot actor when a chat instance goes down with work still in flight.

Search Document