Quick Start
Works out of the box
Resilience is on by default — no config needed. Every bot adapter already retries transient failures automatically.
How It Works
When a send fails, the adapter classifies the error:| State | What happened | What happens next |
|---|---|---|
| Success | Platform returned 2xx | Done — no further action |
| Transient retry | 429 / 503 / network error | Wait (honouring Retry-After) and retry |
| Exhausted → DLQ | Max attempts reached on transient error | Parked in DLQ if dlq_path is set; exception re-raised |
| Permanent error → DLQ | 4xx (not 429) | Parked in DLQ immediately; exception re-raised |
Which Channels Does This Apply To?
Resilience is on by default for every adapter. The operator only opts into DLQ by settingdlq_path.
| Channel | Default | DLQ on exhaust |
|---|---|---|
| Telegram | ✅ on | ✅ |
| Slack | ✅ on | ✅ |
| Discord | ✅ on | ✅ |
| ✅ on | ✅ | |
| ✅ on | ✅ | |
| Linear | ✅ on | ✅ |
| AgentMail | ✅ on | ✅ |
Before this feature shipped, retry/backoff only existed in the Telegram adapter. It is now the default for every adapter via the shared
OutboundResilienceMixin, with no API change required.Configuration Options
All settings live underoutbound_resilience in your channel config (in praisonai.yml or via BotConfig).
| Option | Type | Default | Description |
|---|---|---|---|
enabled | bool | True | Set to False to opt this channel out — only one attempt, no DLQ park. |
initial_ms | int | 1000 | First backoff delay in milliseconds. |
max_ms | int | 10000 | Maximum backoff delay (caps exponential growth). |
factor | float | 1.5 | Multiplicative growth factor per retry. |
max_attempts | int | 3 | Total attempts before parking (1 = no retry). |
jitter | float | 0.25 | Random jitter fraction (0.0–1.0) applied to each delay. |
dlq_path | str | None | None | Path to the SQLite DLQ file. When set, exhausted/permanent failures are parked here for replay. |
Common Patterns
Opt one channel out:Best Practices
Default values are sane — only tune when you see real DLQ growth
Default values are sane — only tune when you see real DLQ growth
The defaults (
initial_ms=1000, max_ms=10000, max_attempts=3) handle the vast majority of transient failures. Only adjust them after observing DLQ entries accumulating in your SQLite file — that signals the defaults are too aggressive or too conservative for your traffic.Use a persistent dlq_path (not /tmp or a container tmpfs)
Use a persistent dlq_path (not /tmp or a container tmpfs)
Store the DLQ on a persistent, local filesystem path. If you use
/tmp or an in-container tmpfs, parked failures are lost on restart — defeating the purpose of the DLQ.Don't disable resilience just because retries are slow — increase factor or max_ms instead
Don't disable resilience just because retries are slow — increase factor or max_ms instead
Slow retries usually mean the platform backoff is long (e.g.,
Retry-After: 60). The mixin already honours Retry-After headers automatically. Increase max_ms to allow longer waits rather than disabling resilience entirely.Pair with Durable Delivery when you need crash-safety across process restarts
Pair with Durable Delivery when you need crash-safety across process restarts
Outbound Resilience retries within a single process lifetime. If your process crashes mid-retry, the message is lost (unless
dlq_path is set and the entry was already parked). For full crash-safe delivery with SQLite outbox and startup drain, use Durable Delivery alongside Outbound Resilience.Related
Durable Outbound Delivery
SQLite outbox for crash-safe delivery with send_durable() and startup drain
Inbound DLQ
Dead-letter queue for failed inbound message processing
Bot Rate Limiting
Per-user and per-channel rate limiting for bot commands
Gateway Channel Config
Full reference for all per-channel configuration options

