MsgMesh Docs

Concepts · delivery guarantees · how to connect ・ public beta

MsgMesh is a managed durable event bus: publish once, and people and AI receive it together. If the other side is offline, the message is kept — they can catch up later and replay it. Queuing, retries, backfill, reconnect: the platform handles that plumbing.

This page is about the mental model and the semantics — what each noun means, which of the four receive modes fits what, and what "at-least-once" actually looks like in your code. For per-endpoint request/response shapes see the API reference; to start typing, Quick start has copy-pasteable snippets.

1.Core concepts

The whole system has four nouns. Get these straight and the rest follows:

NounWhat it is
topicA named stream of messages, e.g. orders, chat. Publishing and subscribing both happen per topic. Topics belong to your tenant and are isolated from everyone else's.
messageA payload in whatever shape you choose (usually JSON). The platform does not interpret it — it only delivers it reliably and keeps it until the retention window ends.
roomA routing label inside a topic — split one chat topic into room-42, room-43, and so on. It decouples "number of rooms" from "number of topics".
key / tokenYour credential. Long-lived API keys stay server-side; browsers only ever hold a short-lived token your backend mints for them.

There are no queues, exchanges, or bindings to design up front — create a topic and start publishing.

2.Four ways to receive

One topic can be consumed several ways at once. What differs is who initiates, and how long the connection lives:

ModeGood forWatch out
Long-pollingBackend services, batch processing, environments before Node 22. A consumer group tracks progress, so a restart picks up where it left off.It is a whole-topic firehose — it cannot receive just one room.
SSELive updates in a browser. The native EventSource reconnects on its own, so you can connect without installing anything.Receive-only; publishing goes over ordinary HTTP.
WebSocketWhen a middlebox blocks SSE, or you already run WebSocket infrastructure.No native reconnect — use the official SDK, which already handles backoff and token rotation.
WebhookWhen you want the platform to call your HTTPS endpoint and hold no long connection at all.The target must be a publicly reachable https URL; anything pointing inward is rejected (below). An endpoint that is temporarily down is retried; one that answers with a permanent rejection such as 404 is not retried and is dead-lettered on the first attempt (below).

3.Delivery guarantees: at-least-once, resume, dedupe

This is the section most worth reading to the end, because it decides whether your code has to handle duplicates.

At-least-once. The platform guarantees a message is not lost because you disconnected — it does not guarantee exactly one delivery. On reconnect the server backfills from where you left off, and at the boundary it will occasionally resend one or two you already saw.

Resume runs on a cursor. Every message carries a monotonically increasing <partition>-<offset> cursor. Your client remembers the last one it saw and sends it back on reconnect, and the server picks up from there — what you missed while disconnected is backfilled rather than dropped.

Deduping is the client's job — and the SDK already does it. The official SDKs (JavaScript and Python) dedupe per partition by cursor: an offset no greater than the highest already seen for that partition is skipped. So with the SDK, what reaches your onMessage is already duplicate-free.

When it cannot backfill, it says so. If you were gone longer than the server's replay window, it emits a msgmesh-resync signal, meaning "I cannot guarantee completeness". Fetch a fresh snapshot yourself; subsequent live messages dedupe against it by cursor.

Ordering. Publish order is preserved within a partition. To keep a group of messages strictly ordered, put them in the same room (a room is the partition key).

4.Credentials: server-side keys vs browser tokens

Long-lived API keys stay server-side. A key is shown in plaintext exactly once, at creation; after that the platform stores only a hash. Never commit one, never log one.

Never put an API key in a browser — anything in the frontend leaks. The right shape is a token broker: your backend holds the key, calls POST /v1/tokens, and hands the frontend a short-lived, downscoped token — so the frontend only ever holds something that expires. The official SDKs support this directly (give them a callback that fetches a token); caching, refetching before expiry, and rotating on reconnect are all handled for you.

Permissions come in two layers. Role keys are admin / producer / consumer; when you need finer control, capability keys spell out which operations × which topics × which rooms. Downscoping may only narrow, and overreach is rejected with 403.

5.Rooms: routing vs isolation

Rooms have two layers, and the distinction matters because the first one alone provides no security:

① Routing (filtering). Publish with a room key and subscribe with a room, and you receive only that room. Omitting the room receives the whole topic (backward compatible).

② Isolation (platform-enforced). Name the allowed rooms in the credential's capabilities and the platform enforces that it can only send and receive those rooms, returning 403 on overreach.

6.Errors and status codes

Any non-2xx response returns {"error": "..."}; the SDKs raise a typed error you can match with instanceof.

StatusMeaningWhat to do
400 / 422Invalid arguments or bodyFix the request. Retrying is pointless.
401Credential invalid or goneTerminal. Obtain a new credential; do not retry indefinitely.
403Insufficient scope, or a billing suspensionPossibly recoverable (topping up lifts a suspension automatically) — back off and retry.
404Resource does not existCheck the name. With the strict topic gate on, a topic that was never created is also a 404.
429Rate limit or included quota exceededBack off and retry; if it persists, it is time to change plan.

5xx responses carry a request_id — include it when reporting a problem and it can be traced directly.

7.Connect in three minutes

Register in the panel and issue a key (shown in plaintext only once), then:

npm i @msgmesh/sdk        # JavaScript / TypeScript
pip install msgmesh        # Python (same API, snake_case)
import { MsgMesh } from "@msgmesh/sdk";

const mq = new MsgMesh({
  apiKey: process.env.MSGMESH_KEY,   // long-lived key, server-side only
  controlPlaneUrl: "...", gatewayUrl: "...", realtimeUrl: "...",
});

await mq.createTopic("orders");
await mq.publish("orders", { hello: 1 });
const msgs = await mq.poll("orders", { group: "g1" });

You do not need the SDK — it is all ordinary HTTP: send Authorization: Bearer <key> to the matching endpoint. Shapes are in the API reference.

8.For AI agents (MCP)

MsgMesh ships an official MCP server, so any MCP-capable AI tool (Claude Code, for example) can treat an event stream as an input source: the agent waits on watch_topic, wakes when something is published, receives only the new events, and decides what to do.

npx @msgmesh/mcp-server     # only MQ_API_KEY is required

This is not "have the agent poll every minute" — it is woken by events, so it neither spins idly nor misses what happened in between.

9.Next steps

  • Quick start — copy-pasteable snippets (SDK / curl / MCP config).
  • API reference — request and response shapes for every endpoint.
  • Live demo — no sign-up; open it and watch events flow.
  • Official examples — full projects you can clone and run.
  • Pricing — plans and quotas. Free during public beta; final pricing is not yet set.

Questions, or want help connecting? Email [email protected].

This document describes MsgMesh's behaviour during public beta and may be updated as the service evolves. The authoritative per-endpoint reference is the API reference. Where the Chinese and English versions differ, the Chinese version governs.