This page covers two things: the runtime metrics every Bot exposes (part of
@botmesh/core) and the optional operational packages (event journal, inbox,
CRM, campaigns…). To send one message to many chats or start a flow from a
job, see Broadcasts and
Starting a flow programmatically.
To survive crashes without losing or duplicating messages (durable inbox/outbox,
retries, dead-letter queue), see Durable message processing.
For health checks and the production checklist, see
Deployment and Going to production.
bot.metrics() returns an in-process snapshot of counters only the engine
knows. It is plain JSON and nothing is persisted: counters are monotonic
since the bot was constructed, or since bot.resetMetrics() (which zeroes
them and keeps the uptime).
messages.in counts inbound messages and interactions accepted after
the access lists and dedupe. out / outFailed count the sends the
provider accepted or rejected.
dropped.duplicate counts redeliveries dropped by
inbound dedupe, blocked the messages,
interactions and calls dropped by the blacklist / whitelist (every
delivery, since blocked input is never recorded by dedupe), and handoff the messages not routed because the chat is
handed off to a human. echo (present only with
echoFilter on) counts the echoes of the bot’s
own sends that the filter dropped.
presenceSkipped.typing / markRead count the presence hints
(ctx.typing(), ctx.recording(), the automatic typing indicator,
ctx.markRead()) skipped because the transport was disconnected or stopped
(best-effort, see Channels).
flows is keyed by conversation/flow name. avgDurationMs is the mean
duration of the conversations that already ended (0 when none did).
A flow that returns and a goto count as completed; c.end() counts as
ended; a failed .guard() (farewell or redirect) counts as guarded;
/cancel, /reset, a replacement or bot.stop() count as cancelled.
toPrometheus(snapshot, { prefix?, labels? }) renders a snapshot in the
Prometheus text format (0.0.4), with no dependencies. Serve it with
PROMETHEUS_CONTENT_TYPE:
conversation:started fires whenever a flow or bot.conversation is
entered: a trigger match, bot.trigger(), a dormant resume or a c.goto().
conversation:ended adds outcome ('completed' | 'ended' | 'guarded' | 'cancelled' | 'timedOut' | 'retriesExceeded' | 'error'), completed (shorthand for
outcome === 'completed'), durationMs and, when it ended with a goto,
the target in goto (a new started follows).
id is unique per conversation instance, so it pairs an ended with its
started. key is the runtime key (chatId, or chatId:userId for a
group participant). When a conversation is replaced, its ended
(outcome: 'cancelled') is emitted before the new one’s started.
A listener that throws is reported on bot.on('error'); it never breaks
the conversation.
@botmesh/events is published on npm (pnpm add @botmesh/events).
Product packages — not publishedThe product packages (@botmesh/inbox,
@botmesh/crm, @botmesh/hub, @botmesh/campaigns and @botmesh/deployments)
are real and persistent: each has a storage contract, an in-RAM reference
store and a Kv* adapter over any DurableKv of the core (SQLite, PostgreSQL,
Redis, Redis Cluster or a file), with a shared conformance suite and opt-in
real-server suites. They live only in the repository, and their APIs may still change.
The operational packages are transport-neutral and scoped by tenant.
Use MemoryEventJournal in tests. FileEventJournal.open('./data/events.json') persists a single-process journal with atomic snapshots, idempotency, leases, retries and replay. For several processes on one host, SharedFileEventJournal.open(path) shares one file under a cross-process mutex; across hosts, implement LeasedEventJournal over PostgreSQL, Redis or a broker. The file journals store JSON: an envelope with a BigInt or a circular reference is rejected with a TypeError (nothing is stored), and a Date reads back as its ISO string. A record that would not read back as a valid one (a missing partitionKey, a NaN or DateoccurredAt, a toJSON that changes the event id) is rejected the same way, in every journal, so nothing a journal accepts can stop it from reopening. Every SharedFileEventJournal operation takes the mutex and re-reads the file (milliseconds on Linux, tens of milliseconds on Windows), so keep it for modest volumes.EventWorker claims events, invokes the handler, acknowledges successful work and moves exhausted retries to the dead-letter state.Every claim hands out a fencing token (record.fencingToken, strictly increasing per event and never reused, not even after replay). The worker passes it back to ack/fail, and the journal rejects a superseded token with StaleLeaseError: a worker whose lease expired while its handler was still running cannot complete, retry or dead-letter an event another worker has re-claimed. runBatch() reports those as stale ({ processed, failed, stale }), and it skips a record whose lease already ran out before its handler started, handing it back with journal.release(eventId, { now, fencingToken }) so the skip does not use up an attempt. Handlers still run at least once, so keep them idempotent (the envelope’s idempotencyKey is there for that). new EventWorker(journal, { leaseMs, clock }) sets the lease length and the time source. An EventJournal adapter for PostgreSQL or Redis must check the token (in ack, fail and release) in the same atomic update that changes the state.The same journal can also be a bot’s durability backend (eventsDurability({ journal }) from @botmesh/events/durability, see Durable message processing). The bot’s inbound records are written on their own channel ('botwa:durable'). list() and replay() show them, but an EventWorker never claims them: a claim without a channel only takes records that have none. The bot processes them itself.
@botmesh/inbox provides optimistic-concurrency assignment (a compare-and-set on version), handoff, notes and unread state. Project ledger StoredMessage entries with inbox.projectMessage(message) so conversations can be rebuilt across channels; attachInbox(bot, inbox) does it for a running bot, inbound messages and accepted sends alike, without delaying either; a projection failure goes to the bot’s 'error' event ({ error }) or, with nobody listening, to the bot logger. Without a tenantId option the conversations go under the bot’s own tenant (BotConfig.tenantId). Persist with new Inbox(new KvInboxStore(kv)). Notes are paged with listNotes(tenant, conversation, { limit, after }) (after = the last note id of the previous page), and inbox.erase(tenant, conversation) deletes a conversation with its notes (right to erasure; erase the messages in the bot store too).@botmesh/crm stores contact channels, tags, attributes and marketing consent, with rule-based segments (tagsAll, attributes compared strictly, consent). ContactDirectory is async over MemoryContactStore or KvContactStore; setConsent retries concurrent writers instead of overwriting them, and erase(tenant, id) deletes a contact (right to erasure). Always segment campaigns using tenant-scoped data and consent.
@botmesh/campaigns skips opt-outs, mismatched segments and duplicate recipients. The store reserves one send slot per (tenant, campaign, recipient) with a TTL; markSent counts the send and markFailed releases the slot. sendCampaign(bot, planner, …) sends with bot.broadcast() and settles each delivery from the real result (accepted, failed, skipped by a hook or cancelled), matched by the target chat even when the provider answers with a canonical id; if the broadcast itself rejects, every reserved slot is released.@botmesh/hub validates plugin manifests, runtime compatibility and granted permissions before enabling a plugin. Manifests are treated as untrusted JSON (every field is type-checked and unknown permissions are rejected), and enabling re-checks the grants atomically, so a concurrent re-install with more permissions is never enabled without them. Installs and enable/disable are persisted per tenant; it does not execute or sandbox untrusted code by itself.@botmesh/deployments tracks immutable versions and one active deployment per tenant/bot, with reservations for running gates and an audit log of gate runs. list(tenant, bot, { limit, after }) pages the versions, prune(tenant, bot, keep) deletes the ones older than the newest keep (never the active one), and new DeploymentRegistry(store, { retainVersions }) prunes after every publish so the version list stays bounded. Connect it to your deployment system for gradual rollout and physical rollback.All five share the same storage model: Memory…Store (reference), Kv…Store over any DurableKv, a conformance suite in test-utils/, and tenant isolation enforced by the backend table name (@botmesh/tenant-kv). Every write is validated before it reaches the backend (a malformed field fails with a TypeError naming it), so one bad write can never make a tenant’s listings unreadable.
bot.metrics() counts; traces explain. With BotConfig.telemetry the bot emits a span tree per message (inbound → dispatch → handler / conversation turn → AI model and tool calls → send), with ids hashed by default and no message content. @botmesh/otel sends those spans to OpenTelemetry (Jaeger, Grafana Tempo, any OTLP backend) and publishes bot.metrics() as OpenTelemetry metrics (bot.plug(otelMetricsPlugin())). Setup, span catalogue, personal-data policy and a Jaeger / Grafana Tempo example: Tracing.
{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"botmesh","description":"A transport-agnostic TypeScript framework for messaging bots: WhatsApp (Baileys or the Cloud API), Telegram, Discord and more behind one IProvider.","colors":{"primary":"#3451B2","light":"#A8B1FF","dark":"#3451B2"},"logo":{"light":"https://mintcdn.com/afaya/32LyKbpS8BIF3Yl-/logo/light.svg?fit=max\u0026auto=format\u0026n=32LyKbpS8BIF3Yl-\u0026q=85\u0026s=5a9c4db10ba52bbd645a009cc009e31e","dark":"https://mintcdn.com/afaya/32LyKbpS8BIF3Yl-/logo/dark.svg?fit=max\u0026auto=format\u0026n=32LyKbpS8BIF3Yl-\u0026q=85\u0026s=be155346190b752c815b6d06f1134702"},"favicon":"https://mintcdn.com/afaya/32LyKbpS8BIF3Yl-/favicon.svg?fit=max\u0026auto=format\u0026n=32LyKbpS8BIF3Yl-\u0026q=85\u0026s=abe4b13c733adb336381bebc0220d3e5","navbar":{"links":[{"href":"https://www.npmjs.com/package/@botmesh/core","label":"npm"}],"primary":{"type":"github","href":"https://github.com/andresayac/botmesh"}},"footer":{"socials":{"github":"https://github.com/andresayac/botmesh"},"links":[{"header":"Project","items":[{"label":"GitHub","href":"https://github.com/andresayac/botmesh"},{"label":"npm","href":"https://www.npmjs.com/package/@botmesh/core"},{"label":"MIT License","href":"https://github.com/andresayac/botmesh/blob/main/LICENSE"},{"label":"Changelog","href":"https://github.com/andresayac/botmesh/blob/main/CHANGELOG.md"}]}]},"search":{"prompt":"Search the botmesh docs…"},"seo":{"indexing":"navigable"},"icons":{"library":"lucide"},"contextual":{"options":["copy","view","chatgpt","claude","perplexity","mcp","cursor","vscode"],"display":"header"},"styling":{"latex":true},"navigation":{"languages":[{"language":"en","default":true,"href":""},{"language":"es","footer":{"socials":{"github":"https://github.com/andresayac/botmesh"},"links":[{"header":"Proyecto","items":[{"label":"GitHub","href":"https://github.com/andresayac/botmesh"},{"label":"npm","href":"https://www.npmjs.com/package/@botmesh/core"},{"label":"Licencia MIT","href":"https://github.com/andresayac/botmesh/blob/main/LICENSE"},{"label":"Changelog","href":"https://github.com/andresayac/botmesh/blob/main/CHANGELOG.md"}]}]},"href":"es"}]}},"deployment":{"deploymentMetadataValue":{"subdomain":"afaya","hasAudioNarration":false,"actualSubdomain":"afaya","customDomains":[],"gitSource":{"type":"github","owner":"andresayac","repo":"botmesh-docs","deployBranch":"main","contentDirectory":"","isPrivate":false,"mountPath":"","label":"","_id":"6ae0b3e80b849c49dba39507"},"entitlements":{"AI_CHAT":{"status":"ENABLED"},"WEB_MCP":{"status":"ENABLED"},"NARRATION":{"status":"ENABLED"}},"buildId":"6ae0b3fe0b849c49dba39747","clientVersion":"0.0.3971","searchFilterCounts":[{"language":"en","tag":"API reference","count":541},{"language":"en","tag":"@botmesh/core","count":373},{"language":"en","tag":"@botmesh/core/testing","count":93},{"language":"en","tag":"@botmesh/core/spi","count":75},{"language":"en","tag":"Guide","count":53},{"language":"es","tag":"Guía","count":53},{"language":"en","tag":"Golden transcripts","count":41},{"language":"en","tag":"Durability","count":35},{"language":"en","tag":"Flows","count":34},{"language":"en","tag":"Messages \u0026 context","count":32},{"language":"en","tag":"Forms","count":30},{"language":"es","tag":"Ejemplos","count":29},{"language":"es","tag":"Características","count":25},{"language":"es","tag":"Avanzado","count":19},{"language":"es","tag":"Intermedio","count":9},{"language":"es","tag":"Principiante","count":8},{"language":"es","tag":"Desarrollo y tests","count":8},{"language":"es","tag":"IA","count":6},{"language":"es","tag":"Primeros pasos","count":6},{"language":"es","tag":"Resumen","count":1}]},"assistantConfig":{"enableAskAiButton":false,"enableStarterQuestions":false,"enableContextualStarterQuestions":false,"enableAskAiSearchBar":true},"basePath":"","hasChatPermissions":true,"hasWebMcpPermissions":true,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-v7-file-tree","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"pause-universal-search-reconcile","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"per-site-search-delta","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-private-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-nav-generations","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"byok-sep-25","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"vite-origin","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-agent-model-picker","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-updated-9-15","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-sso-scim","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-signup","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-enabled","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"isAtlas":false,"googleFontsInlined":false},"customCss":[],"page":{"pagePath":"guide/operations","slug":"/guide/operations","pageMetadata":{"title":"Operations: metrics, events, inbox and delivery","sidebarTitle":"Operations","description":null,"href":"/guide/operations"},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={a:`a`,code:`code`,li:`li`,p:`p`,pre:`pre`,span:`span`,strong:`strong`,ul:`ul`,..._provideComponents(),...props.components},{CodeBlock,Heading,Warning}=_components;if(!CodeBlock)_missingMdxReference(`CodeBlock`,true);if(!Heading)_missingMdxReference(`Heading`,true);if(!Warning)_missingMdxReference(`Warning`,true);return _jsxs(_Fragment,{children:[_jsxs(_components.p,{children:[`This page covers two things: the runtime metrics every `,_jsx(_components.code,{children:`Bot`}),` exposes (part of\n`,_jsx(_components.code,{children:`@botmesh/core`}),`) and the optional operational packages (event journal, inbox,\nCRM, campaigns…). To send one message to many chats or start a flow from a\njob, see `,_jsx(_components.a,{href:`/guide/messages#broadcasts`,children:`Broadcasts`}),` and\n`,_jsx(_components.a,{href:`/guide/messages#starting-a-flow-programmatically`,children:`Starting a flow programmatically`}),`.\nTo survive crashes without losing or duplicating messages (durable inbox/outbox,\nretries, dead-letter queue), see `,_jsx(_components.a,{href:`/guide/durability`,children:`Durable message processing`}),`.\nFor health checks and the production checklist, see\n`,_jsx(_components.a,{href:`/guide/deploy#health-checks`,children:`Deployment`}),` and `,_jsx(_components.a,{href:`/guide/production`,children:`Going to production`}),`.`]}),`\n`,_jsx(Heading,{level:`2`,id:`runtime-metrics`,children:`Runtime metrics`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`bot.metrics()`}),` returns an in-process snapshot of counters only the engine\nknows. It is plain JSON and nothing is persisted: counters are monotonic\nsince the bot was constructed, or since `,_jsx(_components.code,{children:`bot.resetMetrics()`}),` (which zeroes\nthem and keeps the uptime).`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`11`,language:`typescript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`typescript`,children:_jsxs(_components.code,{language:`typescript`,numberOfLines:`11`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`const`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#4FC1FF`},children:` m`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:` =`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` bot`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`metrics`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`()`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:`// {`})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:`// startedAt, uptimeMs, uptime,`}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:` // uptime: '3d 4h', '5m 12s'...`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:`// conversations: { started, completed, ended, guarded, cancelled, timedOut, retriesExceeded, errored, avgDurationMs, active },`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:`// flows: { signup: { started, completed, ended, guarded, cancelled, timedOut, retriesExceeded, errored, avgDurationMs } },`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:`// messages: { in, out, outFailed },`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:`// dropped: { duplicate, blocked, handoff, echo? },`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:`// presenceSkipped: { typing, markRead },`})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:`// triggered,`}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:` // bot.trigger() calls that started a conversation`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:`// errors,`}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:` // errors reported on bot.on('error')`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:`// }`})}),`\n`]})})}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`messages.in`}),` counts inbound messages and interactions accepted after\nthe access lists and dedupe. `,_jsx(_components.code,{children:`out`}),` / `,_jsx(_components.code,{children:`outFailed`}),` count the sends the\nprovider accepted or rejected.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`dropped.duplicate`}),` counts redeliveries dropped by\n`,_jsx(_components.a,{href:`/guide/concepts#inbound-dedupe`,children:`inbound dedupe`}),`, `,_jsx(_components.code,{children:`blocked`}),` the messages,\ninteractions and calls dropped by the blacklist / whitelist (every\ndelivery, since blocked input is never recorded by dedupe), and `,_jsx(_components.code,{children:`handoff`}),` the messages not routed because the chat is\n`,_jsx(_components.a,{href:`/guide/handoff`,children:`handed off to a human`}),`. `,_jsx(_components.code,{children:`echo`}),` (present only with\n`,_jsx(_components.a,{href:`/guide/concepts#echo-filter`,children:_jsx(_components.code,{children:`echoFilter`})}),` on) counts the echoes of the bot’s\nown sends that the filter dropped.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`presenceSkipped.typing`}),` / `,_jsx(_components.code,{children:`markRead`}),` count the presence hints\n(`,_jsx(_components.code,{children:`ctx.typing()`}),`, `,_jsx(_components.code,{children:`ctx.recording()`}),`, the automatic typing indicator,\n`,_jsx(_components.code,{children:`ctx.markRead()`}),`) skipped because the transport was disconnected or stopped\n(best-effort, see `,_jsx(_components.a,{href:`/guide/channels#cross-channel-contract`,children:`Channels`}),`).`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`flows`}),` is keyed by conversation/flow name. `,_jsx(_components.code,{children:`avgDurationMs`}),` is the mean\nduration of the conversations that already ended (`,_jsx(_components.code,{children:`0`}),` when none did).`]}),`\n`,_jsxs(_components.li,{children:[`A flow that returns and a `,_jsx(_components.code,{children:`goto`}),` count as `,_jsx(_components.code,{children:`completed`}),`; `,_jsx(_components.code,{children:`c.end()`}),` counts as\n`,_jsx(_components.code,{children:`ended`}),`; a failed `,_jsx(_components.code,{children:`.guard()`}),` (farewell or `,_jsx(_components.code,{children:`redirect`}),`) counts as `,_jsx(_components.code,{children:`guarded`}),`;\n`,_jsx(_components.code,{children:`/cancel`}),`, `,_jsx(_components.code,{children:`/reset`}),`, a replacement or `,_jsx(_components.code,{children:`bot.stop()`}),` count as `,_jsx(_components.code,{children:`cancelled`}),`.`]}),`\n`]}),`\n`,_jsx(Heading,{level:`3`,id:`prometheus`,children:`Prometheus`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`toPrometheus(snapshot, { prefix?, labels? })`}),` renders a snapshot in the\nPrometheus text format (0.0.4), with no dependencies. Serve it with\n`,_jsx(_components.code,{children:`PROMETHEUS_CONTENT_TYPE`}),`:`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`7`,language:`typescript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`typescript`,children:_jsxs(_components.code,{language:`typescript`,numberOfLines:`7`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`import`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` { `}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`createServer`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` } `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`from`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` 'node:http'`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`import`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` { `}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`toPrometheus`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`PROMETHEUS_CONTENT_TYPE`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` } `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`from`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` '@botmesh/core'`})]}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`createServer`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`((`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`_req`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`res`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`) `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`=>`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` {`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` res`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`setHeader`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`'Content-Type'`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#4FC1FF`},children:`PROMETHEUS_CONTENT_TYPE`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`)`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` res`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`end`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`toPrometheus`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`bot`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`metrics`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(), { `}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`labels:`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` { `}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`bot:`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` 'sales'`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` } }))`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`}).`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`listen`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`9464`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`)`})]}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`The default prefix is `,_jsx(_components.code,{children:`botmesh_`}),` (`,_jsx(_components.code,{children:`prefix: 'botwa_'`}),` keeps the names of the alpha releases). Families: `,_jsx(_components.code,{children:`uptime_seconds`}),`,\n`,_jsx(_components.code,{children:`conversations_active`}),`, `,_jsx(_components.code,{children:`conversations_started_total`}),`,\n`,_jsx(_components.code,{children:`conversations_ended_total{outcome}`}),` (`,_jsx(_components.code,{children:`completed`}),` / `,_jsx(_components.code,{children:`ended`}),` / `,_jsx(_components.code,{children:`guarded`}),` /\n`,_jsx(_components.code,{children:`cancelled`}),` / `,_jsx(_components.code,{children:`timed_out`}),` / `,_jsx(_components.code,{children:`retries_exceeded`}),` / `,_jsx(_components.code,{children:`error`}),`),\n`,_jsx(_components.code,{children:`conversation_duration_avg_seconds`}),`,\n`,_jsx(_components.code,{children:`flow_started_total{flow}`}),`, `,_jsx(_components.code,{children:`flow_ended_total{flow,outcome}`}),`,\n`,_jsx(_components.code,{children:`flow_duration_avg_seconds{flow}`}),`, `,_jsx(_components.code,{children:`messages_in_total`}),`,\n`,_jsx(_components.code,{children:`messages_out_total{result}`}),` (`,_jsx(_components.code,{children:`sent`}),` / `,_jsx(_components.code,{children:`failed`}),`),\n`,_jsx(_components.code,{children:`inbound_dropped_total{reason}`}),` (`,_jsx(_components.code,{children:`duplicate`}),` / `,_jsx(_components.code,{children:`blocked`}),` / `,_jsx(_components.code,{children:`handoff`}),`, plus `,_jsx(_components.code,{children:`echo`}),` with `,_jsx(_components.code,{children:`echoFilter`}),`),\n`,_jsx(_components.code,{children:`presence_skipped_total{kind}`}),` (`,_jsx(_components.code,{children:`typing`}),` / `,_jsx(_components.code,{children:`mark_read`}),`),\n`,_jsx(_components.code,{children:`triggers_total`}),` and `,_jsx(_components.code,{children:`errors_total`}),`. An invalid prefix or label name throws\n`,_jsx(_components.code,{children:`TypeError`}),`.`]}),`\n`,_jsx(Heading,{level:`3`,id:`conversation-events`,children:`Conversation events`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`2`,language:`typescript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`typescript`,children:_jsxs(_components.code,{language:`typescript`,numberOfLines:`2`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`bot`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`on`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`'conversation:started'`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, ({ `}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`id`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`name`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`chatId`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`userId`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`key`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` }) `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`=>`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` { `}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:`/* ... */`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` })`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`bot`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`on`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`'conversation:ended'`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, ({ `}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`id`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`name`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`chatId`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`outcome`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`completed`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`durationMs`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`goto`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` }) `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`=>`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` { `}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:`/* ... */`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` })`})]}),`\n`]})})}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`conversation:started`}),` fires whenever a flow or `,_jsx(_components.code,{children:`bot.conversation`}),` is\nentered: a trigger match, `,_jsx(_components.code,{children:`bot.trigger()`}),`, a dormant resume or a `,_jsx(_components.code,{children:`c.goto()`}),`.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`conversation:ended`}),` adds `,_jsx(_components.code,{children:`outcome`}),` (`,_jsx(_components.code,{children:`'completed' | 'ended' | 'guarded' | 'cancelled' | 'timedOut' | 'retriesExceeded' | 'error'`}),`), `,_jsx(_components.code,{children:`completed`}),` (shorthand for\n`,_jsx(_components.code,{children:`outcome === 'completed'`}),`), `,_jsx(_components.code,{children:`durationMs`}),` and, when it ended with a `,_jsx(_components.code,{children:`goto`}),`,\nthe target in `,_jsx(_components.code,{children:`goto`}),` (a new `,_jsx(_components.code,{children:`started`}),` follows).`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`id`}),` is unique per conversation instance, so it pairs an `,_jsx(_components.code,{children:`ended`}),` with its\n`,_jsx(_components.code,{children:`started`}),`. `,_jsx(_components.code,{children:`key`}),` is the runtime key (`,_jsx(_components.code,{children:`chatId`}),`, or `,_jsx(_components.code,{children:`chatId:userId`}),` for a\ngroup participant). When a conversation is replaced, its `,_jsx(_components.code,{children:`ended`}),`\n(`,_jsx(_components.code,{children:`outcome: 'cancelled'`}),`) is emitted before the new one’s `,_jsx(_components.code,{children:`started`}),`.`]}),`\n`,_jsxs(_components.li,{children:[`A listener that throws is reported on `,_jsx(_components.code,{children:`bot.on('error')`}),`; it never breaks\nthe conversation.`]}),`\n`]}),`\n`,_jsx(Heading,{level:`2`,id:`operational-packages`,children:`Operational packages`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`@botmesh/events`}),` is published on npm (`,_jsx(_components.code,{children:`pnpm add @botmesh/events`}),`).`]}),`\n`,_jsxs(Warning,{children:[_jsx(_components.p,{children:_jsx(_components.strong,{children:`Product packages — not published`})}),_jsxs(_components.p,{children:[`The product packages (`,_jsx(_components.code,{children:`@botmesh/inbox`}),`,\n`,_jsx(_components.code,{children:`@botmesh/crm`}),`, `,_jsx(_components.code,{children:`@botmesh/hub`}),`, `,_jsx(_components.code,{children:`@botmesh/campaigns`}),` and `,_jsx(_components.code,{children:`@botmesh/deployments`}),`)\nare real and `,_jsx(_components.strong,{children:`persistent`}),`: each has a storage contract, an in-RAM reference\nstore and a `,_jsx(_components.code,{children:`Kv*`}),` adapter over any `,_jsx(_components.code,{children:`DurableKv`}),` of the core (SQLite, PostgreSQL,\nRedis, Redis Cluster or a file), with a shared conformance suite and opt-in\nreal-server suites. They live only in the repository, and their APIs may still change.`]})]}),`\n`,_jsx(_components.p,{children:`The operational packages are transport-neutral and scoped by tenant.`}),`\n`,_jsx(Heading,{level:`3`,id:`event-processing`,children:`Event processing`}),`\n`,_jsxs(_components.p,{children:[`Use `,_jsx(_components.code,{children:`MemoryEventJournal`}),` in tests. `,_jsx(_components.code,{children:`FileEventJournal.open('./data/events.json')`}),` persists a single-process journal with atomic snapshots, idempotency, leases, retries and replay. For several processes on one host, `,_jsx(_components.code,{children:`SharedFileEventJournal.open(path)`}),` shares one file under a cross-process mutex; across hosts, implement `,_jsx(_components.code,{children:`LeasedEventJournal`}),` over PostgreSQL, Redis or a broker. The file journals store JSON: an envelope with a `,_jsx(_components.code,{children:`BigInt`}),` or a circular reference is rejected with a `,_jsx(_components.code,{children:`TypeError`}),` (nothing is stored), and a `,_jsx(_components.code,{children:`Date`}),` reads back as its ISO string. A record that would not read back as a valid one (a missing `,_jsx(_components.code,{children:`partitionKey`}),`, a `,_jsx(_components.code,{children:`NaN`}),` or `,_jsx(_components.code,{children:`Date`}),` `,_jsx(_components.code,{children:`occurredAt`}),`, a `,_jsx(_components.code,{children:`toJSON`}),` that changes the event id) is rejected the same way, in every journal, so nothing a journal accepts can stop it from reopening. Every `,_jsx(_components.code,{children:`SharedFileEventJournal`}),` operation takes the mutex and re-reads the file (milliseconds on Linux, tens of milliseconds on Windows), so keep it for modest volumes.`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`EventWorker`}),` claims events, invokes the handler, acknowledges successful work and moves exhausted retries to the dead-letter state.`]}),`\n`,_jsxs(_components.p,{children:[`Every claim hands out a `,_jsx(_components.strong,{children:`fencing token`}),` (`,_jsx(_components.code,{children:`record.fencingToken`}),`, strictly increasing per event and never reused, not even after `,_jsx(_components.code,{children:`replay`}),`). The worker passes it back to `,_jsx(_components.code,{children:`ack`}),`/`,_jsx(_components.code,{children:`fail`}),`, and the journal rejects a superseded token with `,_jsx(_components.code,{children:`StaleLeaseError`}),`: a worker whose lease expired while its handler was still running cannot complete, retry or dead-letter an event another worker has re-claimed. `,_jsx(_components.code,{children:`runBatch()`}),` reports those as `,_jsx(_components.code,{children:`stale`}),` (`,_jsx(_components.code,{children:`{ processed, failed, stale }`}),`), and it skips a record whose lease already ran out before its handler started, handing it back with `,_jsx(_components.code,{children:`journal.release(eventId, { now, fencingToken })`}),` so the skip does not use up an attempt. Handlers still run at least once, so keep them idempotent (the envelope’s `,_jsx(_components.code,{children:`idempotencyKey`}),` is there for that). `,_jsx(_components.code,{children:`new EventWorker(journal, { leaseMs, clock })`}),` sets the lease length and the time source. An `,_jsx(_components.code,{children:`EventJournal`}),` adapter for PostgreSQL or Redis must check the token (in `,_jsx(_components.code,{children:`ack`}),`, `,_jsx(_components.code,{children:`fail`}),` and `,_jsx(_components.code,{children:`release`}),`) in the same atomic update that changes the state.`]}),`\n`,_jsxs(_components.p,{children:[`The same journal can also be a bot’s durability backend (`,_jsx(_components.code,{children:`eventsDurability({ journal })`}),` from `,_jsx(_components.code,{children:`@botmesh/events/durability`}),`, see `,_jsx(_components.a,{href:`/guide/durability#an-@botmesh/events-journal`,children:`Durable message processing`}),`). The bot’s inbound records are written on their own `,_jsx(_components.strong,{children:`channel`}),` (`,_jsx(_components.code,{children:`'botwa:durable'`}),`). `,_jsx(_components.code,{children:`list()`}),` and `,_jsx(_components.code,{children:`replay()`}),` show them, but an `,_jsx(_components.code,{children:`EventWorker`}),` never claims them: a claim without a `,_jsx(_components.code,{children:`channel`}),` only takes records that have none. The bot processes them itself.`]}),`\n`,_jsx(Heading,{level:`3`,id:`inbox-and-crm`,children:`Inbox and CRM`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`@botmesh/inbox`}),` provides optimistic-concurrency assignment (a compare-and-set on `,_jsx(_components.code,{children:`version`}),`), handoff, notes and unread state. Project ledger `,_jsx(_components.code,{children:`StoredMessage`}),` entries with `,_jsx(_components.code,{children:`inbox.projectMessage(message)`}),` so conversations can be rebuilt across channels; `,_jsx(_components.code,{children:`attachInbox(bot, inbox)`}),` does it for a running bot, inbound messages and accepted sends alike, without delaying either; a projection failure goes to the bot’s `,_jsx(_components.code,{children:`'error'`}),` event (`,_jsx(_components.code,{children:`{ error }`}),`) or, with nobody listening, to the bot logger. Without a `,_jsx(_components.code,{children:`tenantId`}),` option the conversations go under the bot’s own tenant (`,_jsx(_components.code,{children:`BotConfig.tenantId`}),`). Persist with `,_jsx(_components.code,{children:`new Inbox(new KvInboxStore(kv))`}),`. Notes are paged with `,_jsx(_components.code,{children:`listNotes(tenant, conversation, { limit, after })`}),` (`,_jsx(_components.code,{children:`after`}),` = the last note id of the previous page), and `,_jsx(_components.code,{children:`inbox.erase(tenant, conversation)`}),` deletes a conversation with its notes (right to erasure; erase the messages in the bot store too).`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`@botmesh/crm`}),` stores contact channels, tags, attributes and marketing consent, with rule-based segments (`,_jsx(_components.code,{children:`tagsAll`}),`, `,_jsx(_components.code,{children:`attributes`}),` compared strictly, consent). `,_jsx(_components.code,{children:`ContactDirectory`}),` is async over `,_jsx(_components.code,{children:`MemoryContactStore`}),` or `,_jsx(_components.code,{children:`KvContactStore`}),`; `,_jsx(_components.code,{children:`setConsent`}),` retries concurrent writers instead of overwriting them, and `,_jsx(_components.code,{children:`erase(tenant, id)`}),` deletes a contact (right to erasure). Always segment campaigns using tenant-scoped data and consent.`]}),`\n`,_jsx(Heading,{level:`3`,id:`campaigns-plugins-and-deployments`,children:`Campaigns, plugins and deployments`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`@botmesh/campaigns`}),` skips opt-outs, mismatched segments and duplicate recipients. The store reserves one send slot per `,_jsx(_components.code,{children:`(tenant, campaign, recipient)`}),` with a TTL; `,_jsx(_components.code,{children:`markSent`}),` counts the send and `,_jsx(_components.code,{children:`markFailed`}),` releases the slot. `,_jsx(_components.code,{children:`sendCampaign(bot, planner, …)`}),` sends with `,_jsx(_components.code,{children:`bot.broadcast()`}),` and settles each delivery from the real result (accepted, failed, skipped by a hook or cancelled), matched by the target chat even when the provider answers with a canonical id; if the broadcast itself rejects, every reserved slot is released.`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`@botmesh/hub`}),` validates plugin manifests, runtime compatibility and granted permissions before enabling a plugin. Manifests are treated as untrusted JSON (every field is type-checked and unknown permissions are rejected), and enabling re-checks the grants atomically, so a concurrent re-install with more permissions is never enabled without them. Installs and enable/disable are persisted per tenant; it does not execute or sandbox untrusted code by itself.`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`@botmesh/deployments`}),` tracks immutable versions and one active deployment per tenant/bot, with reservations for running gates and an audit log of gate runs. `,_jsx(_components.code,{children:`list(tenant, bot, { limit, after })`}),` pages the versions, `,_jsx(_components.code,{children:`prune(tenant, bot, keep)`}),` deletes the ones older than the newest `,_jsx(_components.code,{children:`keep`}),` (never the active one), and `,_jsx(_components.code,{children:`new DeploymentRegistry(store, { retainVersions })`}),` prunes after every publish so the version list stays bounded. Connect it to your deployment system for gradual rollout and physical rollback.`]}),`\n`,_jsxs(_components.p,{children:[`All five share the same storage model: `,_jsx(_components.code,{children:`Memory…Store`}),` (reference), `,_jsx(_components.code,{children:`Kv…Store`}),` over any `,_jsx(_components.code,{children:`DurableKv`}),`, a conformance suite in `,_jsx(_components.code,{children:`test-utils/`}),`, and tenant isolation enforced by the backend table name (`,_jsx(_components.code,{children:`@botmesh/tenant-kv`}),`). Every write is validated before it reaches the backend (a malformed field fails with a `,_jsx(_components.code,{children:`TypeError`}),` naming it), so one bad write can never make a tenant’s listings unreadable.`]}),`\n`,_jsx(Heading,{level:`2`,id:`tracing-opentelemetry`,children:`Tracing (OpenTelemetry)`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`bot.metrics()`}),` counts; traces explain. With `,_jsx(_components.code,{children:`BotConfig.telemetry`}),` the bot emits a span tree per message (inbound → dispatch → handler / conversation turn → AI model and tool calls → send), with ids hashed by default and no message content. `,_jsx(_components.code,{children:`@botmesh/otel`}),` sends those spans to OpenTelemetry (Jaeger, Grafana Tempo, any OTLP backend) and publishes `,_jsx(_components.code,{children:`bot.metrics()`}),` as OpenTelemetry metrics (`,_jsx(_components.code,{children:`bot.plug(otelMetricsPlugin())`}),`). Setup, span catalogue, personal-data policy and a Jaeger / Grafana Tempo example: `,_jsx(_components.a,{href:`/guide/tracing`,children:`Tracing`}),`.`]})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"config":{},"pageMetadata":{"title":"Operations: metrics, events, inbox and delivery","sidebarTitle":"Operations","description":null,"href":"/guide/operations"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"Runtime metrics","slug":"runtime-metrics","depth":2,"children":[{"title":"Prometheus","slug":"prometheus","depth":3,"children":[]},{"title":"Conversation events","slug":"conversation-events","depth":3,"children":[]}]},{"title":"Operational packages","slug":"operational-packages","depth":2,"children":[{"title":"Event processing","slug":"event-processing","depth":3,"children":[]},{"title":"Inbox and CRM","slug":"inbox-and-crm","depth":3,"children":[]},{"title":"Campaigns, plugins and deployments","slug":"campaigns-plugins-and-deployments","depth":3,"children":[]}]},{"title":"Tracing (OpenTelemetry)","slug":"tracing-opentelemetry","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":[],"subdomain":"afaya","siteName":"botmesh","title":"Operations: metrics, events, inbox and delivery - botmesh","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{"en":{"href":"/guide/operations","matchLevel":0},"es":{"href":"/es/guide/operations","matchLevel":1}}},"lastModified":"2026-10-27T14:26:08.294Z","originalFileLocation":"guide/operations.mdx","locale":"en"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"system\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"application-name\" content=\"botmesh\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/afaya/omilwj1Ifh1eDwt5/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/afaya/omilwj1Ifh1eDwt5/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/afaya/omilwj1Ifh1eDwt5/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/afaya/omilwj1Ifh1eDwt5/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/afaya/omilwj1Ifh1eDwt5/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/afaya/omilwj1Ifh1eDwt5/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/afaya/omilwj1Ifh1eDwt5/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/afaya/omilwj1Ifh1eDwt5/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/afaya/omilwj1Ifh1eDwt5/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"Operations: metrics, events, inbox and delivery - botmesh\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"botmesh\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://afaya.mintlify.app/guide/operations\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://afaya.mintlify.app/_mintlify/api/og/v2?division=Features\u0026amp;title=Operations%3A+metrics%2C+events%2C+inbox+and+delivery\u0026amp;theme=dea9f3ba838ba9e4866bef64\u0026amp;signature=c27dec0a4a5f51b301cc10dfcbadeb128dbea97f5a61a1ed7139e8214febc0c3\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"Operations: metrics, events, inbox and delivery - botmesh\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://afaya.mintlify.app/_mintlify/api/og/v2?division=Features\u0026amp;title=Operations%3A+metrics%2C+events%2C+inbox+and+delivery\u0026amp;theme=dea9f3ba838ba9e4866bef64\u0026amp;signature=c27dec0a4a5f51b301cc10dfcbadeb128dbea97f5a61a1ed7139e8214febc0c3\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/afaya/omilwj1Ifh1eDwt5/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"botmesh\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#3451B2\">\u003clink data-mint-head rel=\"canonical\" href=\"https://afaya.mintlify.app/guide/operations\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/guide/operations.md\">\u003clink data-mint-head rel=\"alternate\" hreflang=\"en\" href=\"https://afaya.mintlify.app/guide/operations\">\u003clink data-mint-head rel=\"alternate\" hreflang=\"es\" href=\"https://afaya.mintlify.app/es/guide/operations\">\u003clink data-mint-head rel=\"alternate\" hreflang=\"x-default\" href=\"https://afaya.mintlify.app/guide/operations\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://afaya.mintlify.app/#organization\",\"name\":\"botmesh\",\"url\":\"https://afaya.mintlify.app\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/afaya/32LyKbpS8BIF3Yl-/logo/light.svg?fit=max\u0026auto=format\u0026n=32LyKbpS8BIF3Yl-\u0026q=85\u0026s=5a9c4db10ba52bbd645a009cc009e31e\"}},{\"@type\":\"WebSite\",\"@id\":\"https://afaya.mintlify.app#website\",\"name\":\"botmesh\",\"url\":\"https://afaya.mintlify.app\",\"publisher\":{\"@id\":\"https://afaya.mintlify.app/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://afaya.mintlify.app/guide/operations#webpage\",\"url\":\"https://afaya.mintlify.app/guide/operations\",\"name\":\"Operations: metrics, events, inbox and delivery\",\"dateModified\":\"2026-10-27T14:26:08.294Z\",\"isPartOf\":{\"@id\":\"https://afaya.mintlify.app#website\"},\"breadcrumb\":{\"@id\":\"https://afaya.mintlify.app/guide/operations#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://afaya.mintlify.app/guide/operations#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Features\",\"item\":\"https://afaya.mintlify.app/guide/messages\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Operations: metrics, events, inbox and delivery\",\"item\":\"https://afaya.mintlify.app/guide/operations\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://afaya.mintlify.app/guide/operations#article\",\"headline\":\"Operations: metrics, events, inbox and delivery\",\"name\":\"Operations: metrics, events, inbox and delivery\",\"url\":\"https://afaya.mintlify.app/guide/operations\",\"mainEntityOfPage\":{\"@id\":\"https://afaya.mintlify.app/guide/operations#webpage\"},\"dateModified\":\"2026-10-27T14:26:08.294Z\",\"publisher\":{\"@id\":\"https://afaya.mintlify.app/#organization\"},\"isPartOf\":{\"@id\":\"https://afaya.mintlify.app#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"botmesh\",\"creator\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","siteName":"botmesh","nav":{"global":null,"languages":[{"language":"en","default":true,"tabs":[{"tab":"Guide","icon":"book-open","groups":[{"group":"Getting started","pages":[{"title":"botmesh","sidebarTitle":"Home","mode":"wide","href":""},{"title":"Getting Started","sidebarTitle":"Quick start","href":"/guide/getting-started"},{"title":"Your First Bot","sidebarTitle":"Your first bot","href":"/guide/first-bot"},{"title":"Core Concepts","sidebarTitle":"Core concepts","href":"/guide/concepts"},{"title":"Configuration reference","href":"/guide/configuration"},{"title":"Channels (providers)","sidebarTitle":"Channels","href":"/guide/channels"}]},{"group":"Features","pages":[{"title":"Messages \u0026 Media","sidebarTitle":"Messages \u0026 media","href":"/guide/messages"},{"title":"Conversation Flows","sidebarTitle":"Conversation flows","href":"/guide/flows"},{"title":"Special triggers: WELCOME, message types and CALL","sidebarTitle":"Special triggers","href":"/guide/triggers"},{"title":"Forms (WhatsApp Flows)","href":"/guide/forms"},{"title":"Persistent user data (`ctx.user`)","sidebarTitle":"Persistent user data","href":"/guide/user-data"},{"title":"Internationalization (i18n)","href":"/guide/i18n"},{"title":"Human handoff \u0026 access lists","sidebarTitle":"Handoff \u0026 access lists","href":"/guide/handoff"},{"title":"Hooks (Middleware)","sidebarTitle":"Hooks (middleware)","href":"/guide/hooks"},{"title":"Plugins","href":"/guide/plugins"},{"title":"AI plugin","href":"/guide/ai"},{"title":"AI tool calling","href":"/guide/ai-tools"},{"title":"AI memory \u0026 RAG","href":"/guide/ai-memory-rag"},{"title":"Streaming replies (`ctx.stream`)","sidebarTitle":"Streaming replies","href":"/guide/streaming"},{"title":"Voice (`@botmesh/voice`)","sidebarTitle":"Voice (STT/TTS)","href":"/guide/voice"},{"title":"Integrations","href":"/guide/integrations"},{"title":"Dependency Injection","sidebarTitle":"Dependency injection","href":"/guide/di"},{"title":"Scheduler","href":"/guide/scheduler"},{"title":"Rate Limiting (Anti-Ban)","sidebarTitle":"Rate limiting (anti-ban)","href":"/guide/rate-limiting"},{"title":"Persistent store","href":"/guide/store"},{"title":"Control plane","href":"/guide/control-plane"},{"title":"Webhook server (`@botmesh/server`)","sidebarTitle":"Webhook server","href":"/guide/server"},{"title":"24-hour window and pricing (Cloud API)","sidebarTitle":"WhatsApp 24h window","description":null,"href":"/guide/session-window"},{"title":"Operations: metrics, events, inbox and delivery","sidebarTitle":"Operations","href":"/guide/operations"},{"title":"Tracing (OpenTelemetry)","description":null,"href":"/guide/tracing"},{"title":"Durable message processing","sidebarTitle":"Durable processing","href":"/guide/durability"}]},{"group":"Develop \u0026 test","pages":[{"title":"Terminal simulator (`botmesh dev`)","sidebarTitle":"Terminal simulator","href":"/guide/dev-simulator"},{"title":"Platform sandbox","href":"/guide/sandbox"},{"title":"Testing","href":"/guide/testing"},{"title":"Inspect \u0026 lint flows","href":"/guide/flow-tools"},{"title":"JSON flow schema","href":"/guide/flow-schema"},{"title":"MCP server","href":"/guide/mcp"},{"title":"Golden transcripts","href":"/guide/golden-transcripts"},{"title":"ESLint plugin","href":"/guide/eslint"}]},{"group":"Advanced","pages":[{"title":"Sub-APIs (v1) → transport escape hatches (v2)","sidebarTitle":"Sub-APIs","href":"/guide/sub-apis"},{"title":"Going to production","href":"/guide/production"},{"title":"Deployment","href":"/guide/deploy"},{"title":"Running N replicas","href":"/guide/replicas"},{"title":"Architecture","href":"/guide/architecture"},{"title":"Redis Cluster","href":"/guide/redis-cluster"},{"title":"Security","href":"/guide/security"},{"title":"Threat model","href":"/guide/threat-model"},{"title":"Upgrade to stable: checklist and API changes","sidebarTitle":"Upgrade to stable: checklist \u0026 API","href":"/guide/stable-migration"},{"title":"Upgrade to stable: dependencies","href":"/guide/migration"},{"title":"Upgrade to stable: behaviour changes","sidebarTitle":"Upgrade to stable: behaviour","href":"/guide/migration-2"},{"title":"Upgrade notes: channels and plugins","sidebarTitle":"Upgrade notes: channels \u0026 plugins","href":"/guide/upgrade-channels-plugins"},{"title":"Troubleshooting","href":"/guide/troubleshooting"},{"title":"FAQ","href":"/guide/faq"}]}]},{"tab":"Examples","icon":"chef-hat","pages":[{"title":"Examples","href":"/examples"}]},{"tab":"API reference","icon":"code","pages":[{"title":"API reference","href":"/api/core"}]}]},{"language":"es","footer":{"socials":{"github":"https://github.com/andresayac/botmesh"},"links":[{"header":"Proyecto","items":[{"label":"GitHub","href":"https://github.com/andresayac/botmesh"},{"label":"npm","href":"https://www.npmjs.com/package/@botmesh/core"},{"label":"Licencia MIT","href":"https://github.com/andresayac/botmesh/blob/main/LICENSE"},{"label":"Changelog","href":"https://github.com/andresayac/botmesh/blob/main/CHANGELOG.md"}]}]},"pages":[{"title":"es","href":"/es/guide/operations"}]}]},"title":"Operations: metrics, events, inbox and delivery - botmesh","buildId":"dpl_DFJrtvUi8HS5F9nkooKuDJsuD5BM","deploymentId":"dpl_DFJrtvUi8HS5F9nkooKuDJsuD5BM","dynamics":[],"chunks":[]}