You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
PR MervinPraison/PraisonAI#2129 (merged 2026-06-22) restored src/praisonai/praisonai/bots/_delivery.py after PR #2054 accidentally deleted it. The module exposes public, user-facing APIs for durable outbound message delivery from PraisonAI bots:
DurableDelivery
deliver_with_retry
deliver_chunked
DurableAdapterMixin
(companion) OutboundQueue, OutboundEntry from praisonai.bots._outbox
The fact that this regression went undetected for an entire PR cycle is partly because these symbols have zero user documentation in MervinPraison/PraisonAIDocs. The inbound side is documented (docs/features/inbound-journal.mdx), but the outbound side is not. This issue requests a brand-new page covering it.
Decision: NEW content (not an update)
I searched the docs repo for DurableDelivery, deliver_with_retry, DurableAdapterMixin, and outbound delivery topics. Only synced SDK source mirrors hit — no .mdx documentation page exists for outbound durable delivery. So this is a new page, not an update.
The existing docs/features/inbound-journal.mdx is the natural counterpart and should be linked in the "Related" section.
Where the page goes
Per AGENTS.md rules (folder placement):
Path:docs/features/outbound-delivery.mdx (default location for AI-agent-authored pages)
DO NOT place in docs/concepts/ (human-only)
Updatedocs.json to add the page under the Features group, alongside inbound-journal, inbound-dlq, cross-platform-mirror
Suggested sidebar position: right next to inbound-journal so users find the inbound/outbound pair together
SDK source files to READ first (mandatory per AGENTS.md §1.2)
Read these completely before drafting the page — verify every parameter, type, and default against the source:
If outbox is None → just retries via deliver_with_retry and returns success/failure.
If outbox is set → message is persisted before sending, then delivered, then marked sent or failed. Permanent errors (string-prefixed "Permanent error:") are surfaced via OutboundQueue.mark_failed(..., permanent=True).
drain_pending() is for startup recovery — replays anything still pending after a crash.
This is the recommended user-facing entry point — mix it into any custom bot adapter to add durability with one line: self.setup_durable_delivery(outbox_path="~/.praisonai/state/outbox.sqlite", platform="myplatform").
Configuration table to include in the page
Option
Type
Default
Description
outbox_path
str | None
None
SQLite outbox path. None disables durability (falls back to direct send + retry). ~ is expanded; parent dirs auto-created.
platform
str
""
Platform name. Used for error classification and target prefixing (telegram:CHANNEL).
max_attempts
int
3
Max delivery attempts per message before marking failed.
max_size
int
50_000
Max entries kept in the outbox. Excess evicted (sent first, then oldest pending).
ttl_seconds
int
604_800 (7 days)
TTL for sent entries before eviction.
idempotency_key
str | None
auto-uuid
Per-message dedup key. Pass a stable value (e.g. f"{platform}:{message_id}") to prevent double-send on retry.
metadata
dict | None
None
Optional tracking metadata persisted with the entry.
backoff
BackoffPolicy | None
default policy
Exponential-backoff policy from praisonai.bots._resilience.
abort_signal
asyncio.Event | None
None
Cancels in-flight retries (used by deliver_with_retry).
---title: "Outbound Delivery"sidebarTitle: "Outbound Delivery"description: "Crash-safe outbound message delivery with persistence, retries, and chunking"icon: "paper-plane"---
Outbound Delivery persists messages before sending and retries on failure, so a crashed
bot replays unsent messages on restart instead of losing them.
<heromermaidgraphLR—seecolorschemebelow>
## Quick Start
<Steps>
<Steptitle="One-line durability for your bot adapter">
Show `DurableAdapterMixin` + `setup_durable_delivery(outbox_path=...)`
on a custom adapter, then `send_durable(channel_id, "Hello")`.
</Step>
<Steptitle="Standalone durable send (no mixin)">
Show direct `DurableDelivery(outbox, adapter, platform="telegram")`+`await delivery.send(channel_id, content, idempotency_key=...)`.
</Step>
<Steptitle="Replay pending after a crash">
Show `await delivery.drain_pending()` in your bot's startup hook.
</Step>
</Steps>
## How It Works
<sequenceDiagram: User→Agent→DurableDelivery→OutboundQueue(persist)→Adapter→Platform;onfailure:staysinqueue;onstartup:drain_pendingreplays>
| Stage | Purpose | What happens ||---|---|---||**enqueue**| Persistence | Message written to SQLite before any send attempt ||**deliver**| Send with retry | Exponential backoff via `BackoffPolicy`; permanent errors short-circuit ||**mark_sent / mark_failed**| Bookkeeping | Successful → cleared on TTL; failed → retried on next drain ||**drain_pending**| Crash recovery | Replays everything not yet marked sent |## When to use which option
<graphTBdiagramchoosingbetween:>-Bool/nooutbox→just`deliver_with_retry`(transientnetworkblipsonly)-`DurableAdapterMixin.setup_durable_delivery(outbox_path=...)`→recommendedforcustombotadapters-`DurableDelivery(outbox,adapter,...)`directly→whenyouneedfine-grainedcontrol-`deliver_chunked`→longmessagesthatexceedplatformlimits|Need|Use||---|---||Retryonflakynetworkonly|`deliver_with_retry`||Survivecrashes/restarts|`DurableDelivery`with`OutboundQueue`||Customadapter,minimalcode|`DurableAdapterMixin.send_durable(...)`||Longmessages(>4096chars)|`deliver_chunked`(orrelyon`UnifiedDelivery`auto-chunk)|##ConfigurationOptions<fulltablefromabove>
## Per-platform examples
<TabsforTelegram,Discord,Slack,WhatsApp—mirrorinbound-journal.mdxstyle,showingeachadaptersetting`outbox_path`andcalling`send_durable`>##CommonPatterns###Pattern1:Pairwith`InboundJournal`forfullinbound+outbounddurability###Pattern2:Idempotencykeystosurviveduplicatewebhooks###Pattern3:Startupdraininthebot'slifecyclehook###Pattern4:Chunkinglongagentreplies##BestPractices<AccordionGroup>-Useabsolute,persistentpathfor`outbox_path`(not/tmp)-Setastable`idempotency_key`(e.g.`f"{platform}:{message_id}"`)-Alwayscall`drain_outbox()`onstartup-Tune`max_attempts`and`BackoffPolicy`foryourplatform'sratelimits</AccordionGroup>##Related<CardGroupcols={2}>
<Cardtitle="Inbound Journal"href="/docs/features/inbound-journal">Inbound side: dedup + crash-safe replay</Card>
<Cardtitle="Inbound DLQ"href="/docs/features/inbound-dlq">Failure handling on the agent side</Card>
</CardGroup>
✅ Hero Mermaid diagram with the standard color scheme (#8B0000, #189AB4, #10B981, #F59E0B, #6366F1, white text, #7C90A0 stroke).
✅ User-focused / non-developer tone — "Is it really this easy?" feeling. No SDK-style API dump.
✅ Agent-centric: open with how an Agent author wires this in, not internal class diagrams.
✅ Use simple imports only: from praisonai.bots import DurableDelivery, DurableAdapterMixin — never reach into praisonai.bots._delivery.
✅ Every code example must run as-is (real imports, realistic but minimal data, no your-key-here placeholders).
✅ Use <Steps>, <Tabs>, <AccordionGroup>, <CardGroup> Mintlify components.
✅ A "How users will interact with this in real scenarios" flow diagram (sequence diagram showing webhook → durable send → crash → replay → user eventually receives reply).
✅ A decision diagram for "which option do I use?" because there are 4 entry points (deliver_with_retry, deliver_chunked, DurableDelivery, DurableAdapterMixin).
✅ Frontmatter icon: "paper-plane" (outbound) is a good fit; alternative: "truck" or "share".
❌ Do not document internal helpers (UnifiedDelivery, MessageSender Protocol, _send_streamed, etc.) — those are SDK-internal; the auto-generated SDK reference covers them.
❌ Do not modify docs/concepts/ (human-only per AGENTS.md §1.8).
docs.json update
Add the new page under the Features group, ideally adjacent to inbound-journal:
Context
PR MervinPraison/PraisonAI#2129 (merged 2026-06-22) restored
src/praisonai/praisonai/bots/_delivery.pyafter PR #2054 accidentally deleted it. The module exposes public, user-facing APIs for durable outbound message delivery from PraisonAI bots:DurableDeliverydeliver_with_retrydeliver_chunkedDurableAdapterMixinOutboundQueue,OutboundEntryfrompraisonai.bots._outboxThe fact that this regression went undetected for an entire PR cycle is partly because these symbols have zero user documentation in
MervinPraison/PraisonAIDocs. The inbound side is documented (docs/features/inbound-journal.mdx), but the outbound side is not. This issue requests a brand-new page covering it.Decision: NEW content (not an update)
I searched the docs repo for
DurableDelivery,deliver_with_retry,DurableAdapterMixin, and outbound delivery topics. Only synced SDK source mirrors hit — no.mdxdocumentation page exists for outbound durable delivery. So this is a new page, not an update.The existing
docs/features/inbound-journal.mdxis the natural counterpart and should be linked in the "Related" section.Where the page goes
Per
AGENTS.mdrules (folder placement):docs/features/outbound-delivery.mdx(default location for AI-agent-authored pages)docs/concepts/(human-only)docs.jsonto add the page under the Features group, alongsideinbound-journal,inbound-dlq,cross-platform-mirrorinbound-journalso users find the inbound/outbound pair togetherSDK source files to READ first (mandatory per AGENTS.md §1.2)
Read these completely before drafting the page — verify every parameter, type, and default against the source:
MervinPraison/PraisonAI, branchmain)DurableDelivery,deliver_with_retry,deliver_chunked,MessageSender,UnifiedDeliverysrc/praisonai/praisonai/bots/_delivery.pyDurableAdapterMixin,setup_durable_delivery,drain_outbox,send_durablesrc/praisonai/praisonai/bots/_durable_adapter.pyOutboundQueue,OutboundEntrysrc/praisonai/praisonai/bots/_outbox.pyBackoffPolicy,compute_backoff,is_recoverable_error,sleep_with_abortsrc/praisonai/praisonai/bots/_resilience.pyOutboundDeliveryProtocolsrc/praisonaiagents/praisonaiagents/gateway/protocols.pysrc/praisonai/praisonai/bots/__init__.pyRegression test that pins the public surface:
src/praisonai/tests/unit/bots/test_delivery_imports.py.Public symbols and signatures (verified from source — June 2026)
DurableDeliveryBehaviour worth documenting:
outboxis None → just retries viadeliver_with_retryand returns success/failure.outboxis set → message is persisted before sending, then delivered, then marked sent or failed. Permanent errors (string-prefixed"Permanent error:") are surfaced viaOutboundQueue.mark_failed(..., permanent=True).drain_pending()is for startup recovery — replays anything still pending after a crash.deliver_with_retryBehaviour:
BackoffPolicyfrom_resilience.py).is_recoverable_error(e, platform)— non-recoverable returns immediately with"Permanent error: ...".abort_signallets callers cancel mid-retry.deliver_chunkedBehaviour: splits long messages with
chunk_messageand sends each piece;reply_tois only applied to the first chunk.DurableAdapterMixinThis is the recommended user-facing entry point — mix it into any custom bot adapter to add durability with one line:
self.setup_durable_delivery(outbox_path="~/.praisonai/state/outbox.sqlite", platform="myplatform").Configuration table to include in the page
outbox_pathstr | NoneNoneNonedisables durability (falls back to direct send + retry).~is expanded; parent dirs auto-created.platformstr""telegram:CHANNEL).max_attemptsint3max_sizeint50_000ttl_secondsint604_800(7 days)idempotency_keystr | Nonef"{platform}:{message_id}") to prevent double-send on retry.metadatadict | NoneNonebackoffBackoffPolicy | Nonepraisonai.bots._resilience.abort_signalasyncio.Event | NoneNonedeliver_with_retry).Proposed page structure (follows AGENTS.md template)
Mandatory style/quality requirements (from AGENTS.md)
#8B0000,#189AB4,#10B981,#F59E0B,#6366F1, white text,#7C90A0stroke).from praisonai.bots import DurableDelivery, DurableAdapterMixin— never reach intopraisonai.bots._delivery.your-key-hereplaceholders).<Steps>,<Tabs>,<AccordionGroup>,<CardGroup>Mintlify components.deliver_with_retry,deliver_chunked,DurableDelivery,DurableAdapterMixin).icon:"paper-plane"(outbound) is a good fit; alternative:"truck"or"share".UnifiedDelivery,MessageSenderProtocol,_send_streamed, etc.) — those are SDK-internal; the auto-generated SDK reference covers them.docs/concepts/(human-only per AGENTS.md §1.8).docs.json update
Add the new page under the Features group, ideally adjacent to
inbound-journal:{ "group": "Features", "pages": [ "...", "docs/features/inbound-journal", "docs/features/outbound-delivery", // ← new "docs/features/inbound-dlq", "..." ] }Validate
docs.jsonas JSON after editing (AGENTS.md §1.9 rule 8).Acceptance checklist
docs/features/outbound-delivery.mdxexists, follows the template above_delivery.py,_durable_adapter.py,_outbox.pyexactly<Tabs>(Telegram, Discord, Slack, WhatsApp)docs/features/inbound-journal.mdx"Related" sectiondocs.jsonupdated and valid JSONclaude/admiring-euler-1rw6s0per the task instructionsWorking branches
Per the task spec:
claude/confident-fermat-1rw6s0(no code changes needed for this docs issue)claude/admiring-euler-1rw6s0(where the new page should land)References
docs/features/inbound-journal.mdx