OptionalbackendWhere the inbox and outbox records live: MemoryDurability, FileDurability, or a store's
backend (PostgresStore#durability(), RedisStore#durability(), …). Default: a new
MemoryDurability (survives nothing but redeliveries within the process).
OptionaljournalUse DurabilityConfig.backend (same value; the old name of the option).
Setting both to different backends throws a ConfigValidationError.
OptionalinstanceLease owner of this process. Keep it STABLE across restarts of the same deployment (a restarted process then reclaims its own in-flight records at once, without waiting for their leases to expire) and UNIQUE per concurrently running replica (two live processes with one id would take each other's leases).
Default: <hostname>/<botId>/<connectionId> on a single-process backend (memory, file);
on a shared backend (Postgres, Redis) that id plus a per-boot suffix, so two processes
on one host never share it — a restart then waits for the old leases to expire.
Optionalfailfalse (default): when the journal cannot record an inbound event, the event is NOT
dispatched (fail-closed) — an 'error' is emitted and the dedupe key is released so
a transport redelivery is processed. true: dispatch it without the journal.
OptionalinboxOptionalleaseMs?: string | numberProcessing lease (heartbeat-extended while the event is running). Default 60_000 ms.
OptionalmaxAttempts?: numberAttempts before an event whose processing keeps failing is dead-lettered. Default 5.
Optionalbackoff?: RetryBackoffBackoff between failed attempts. Default { baseMs: 1000, capMs: 60000, jitter: 0.2 }.
OptionalmediaRefs?: booleanJournal a serializable reference to each message's media (IProvider.serializeMediaRef)
so a replayed message (crash recovery, background retry) can still download it
(IProvider.rehydrateMediaRef). Only applies to providers with those hooks. The
reference grants access to the media, like the message body stored next to it.
false: journal media tokens as they are, as before (a replayed message may then be
unable to download its media). Default true.
OptionaloutboxOptionalretries?: numberRetries after the first attempt before a send is dead-lettered. Default 5.
Optionalbackoff?: RetryBackoffBackoff between attempts (a RateLimitedError waits at least its retryAfterMs). Default as inbox.backoff.
OptionalinlineMaxMs?: string | numberHow long a ctx.reply / bot.send keeps retrying inline before it rejects with
SendDeferredError and leaves the send to the background sweeper. Default '15s'.
OptionalonAmbiguous?: "skip" | "resend"A send whose outcome is unknown after a crash (the provider was called, the result
was never recorded): 'skip' (default) marks it unknown — possible loss, never a
duplicate; 'resend' sends it again — never a loss, possible duplicate.
OptionalmaxPayloadBytes?: numberEncoded payloads above this size bypass the outbox (sent directly, with a warning). Default 1 MiB.
OptionalleaseMs?: string | numberLease of a send in flight (heartbeat-extended while the provider call runs). Default 60_000 ms.
OptionalonDiverged?: "send" | "skip"A replayed handler whose n-th send differs from the one recorded under the same derived
key (<inboundId>:<n>) took another path (e.g. a non-persistent flow lost on a crash):
'send' (default) sends the new content under a new key — never a lost reply;
'skip' answers it with the recorded ref, as if it were the same send. Explicit
idempotencyKeys are never compared: the caller names the send.
OptionalpollBackground sweeper period (retries, expired leases). Default 1000 ms.
OptionalretentionKeep done/sent records this long before the sweeper purges them. It bounds how long
a late transport redelivery is still recognised as a duplicate. Default '7d'; 0 disables purging.
BotConfig.durability. Every field is optional;{}enables durability with the in-memory backend.