Undelivered-Message Notice
Tell the user and alert operators when a gateway reply can't be delivered
When a gateway reply fails permanently, this sends the user a short “couldn’t deliver” notice and fires a MESSAGE_UNDELIVERED hook so operators can route the failure.
Off by default — existing deployments are unchanged until you opt in.
Quick Start
- Turn the notice on
Set
notify_on_undelivered: truein thegateway:block ofgateway.yaml.yaml gateway: notify_on_undelivered: trueStart the gateway as usual — a permanently-undeliverable reply now sends a one-line notice and fires the hook.
bash praisonai gateway start --config gateway.yaml - Customise the notice text
Add
undelivered_templateto replace the built-in one-line note.yaml gateway: notify_on_undelivered: true undelivered_template: "⚠️ We processed your request but couldn't deliver the reply — please try again."
How It Works
The DeliveryRouter already parks a permanently-failed reply in the DLQ. With the flag on, it also best-effort sends a short notice on the same channel and fires the hook — both fully guarded so they can never mask the original failure.
A large or rich reply may fail while a one-line note still lands, so the notice is attempted even when the original send did not succeed. Whether the note reached the user is reported to the hook as notice_delivered.
Configuration Options
Both keys live under the top-level gateway: block.
| Key | Type | Default | Description |
|---|---|---|---|
notify_on_undelivered | bool | false | Opt in to the last-resort user notice and the MESSAGE_UNDELIVERED hook. |
undelivered_template | str | built-in note | The one-line plain-text notice. Kept short so it slips through where the original reply couldn’t. |
The MESSAGE_UNDELIVERED Hook
The hook fires the moment a reply is confirmed permanently undeliverable, so an operator can mirror, alert, or re-queue it without patching adapters.
from praisonaiagents.hooks import HookRegistry, HookEvent
registry = HookRegistry()
@registry.on(HookEvent.MESSAGE_UNDELIVERED)
def on_undelivered(data):
# data.platform, data.channel_id, data.content, data.error, data.notice_delivered
alert_ops(f"Undelivered on {data.platform}:{data.channel_id} — {data.error}")MessageUndeliveredInput payload fields:
| Field | Type | Description |
|---|---|---|
platform | str | Channel platform (e.g. telegram, slack) |
content | str | The reply that could not be delivered (truncated to 500 chars in to_dict) |
channel_id | str | Target channel/chat id |
error | str | The delivery error that triggered the failure |
notice_delivered | bool | Whether the best-effort user notice actually landed |
To forward this event to a shell command or HTTP sink with no Python, list message_undelivered under Gateway Outbound Hooks.
Common Patterns
Mirror every undelivered reply to a home channel
from praisonaiagents.hooks import HookRegistry, HookEvent
registry = HookRegistry()
@registry.on(HookEvent.MESSAGE_UNDELIVERED)
def mirror(data):
send_to_ops_channel(
f"[{data.platform}] to {data.channel_id} failed: {data.error}\n{data.content}"
)Notice on, but no operator hook
Set only notify_on_undelivered when you just want the user to hear something and don’t yet handle the hook.
gateway:
notify_on_undelivered: trueBest Practices
Keep it off unless silent loss is a problem
The default is OFF and byte-for-byte unchanged. Turn it on for user-facing bots where a silently dropped reply would confuse people.
Keep the template short
The notice exists to slip through where a large/rich reply couldn’t. A one-line plain-text note is far more likely to land than the original.
Use the hook to re-queue, not to retry inline
The reply is already in the DLQ by the time the hook fires. Route it — mirror, alert, or re-queue — rather than assuming the notice guarantees delivery.
Check notice_delivered before you rely on it
notice_delivered tells you whether even the one-line note reached the user. When it’s False, escalate through the operator channel.