Docs updated: 2026-09-15 · public beta

Understand delivery before you connect.

MsgMesh is a managed durable event bus. Publishers send events to a topic; receivers consume them over SSE, WebSocket, Webhook, long-poll or MCP.

This page states the delivery semantics, reconnect behaviour and limits of each path. To build now, go to Quickstart. For endpoint-level requests and responses, use the API reference ↗.

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.at-most-once: the offset commits when you fetch, so if the response is lost in transit that batch cannot be retrieved. 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.

Scope first: this section is about SSE, WebSocket and Webhook. Long-poll is not included — its offset commits when you fetch, making it at-most-once; if the response is lost in transit that batch is never re-sent. Use SSE or Webhook when you cannot miss messages.

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". Receiving it means the catch-up is incomplete — rebuild state from your own business system. The platform has no snapshot storage and no snapshot API. Live messages after that still dedupe 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 exceeded (quota is counted in message operations)Back 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.Evaluation and billing FAQ

How is MsgMesh different from running Kafka yourself?

MsgMesh runs on Kafka, but manages the cluster, HTTP and realtime access, Webhook retries and dead-letter, short-lived tokens and the MCP Server as one service. Run Kafka when you need broker control, cross-region topology or custom stream processing. Use MsgMesh when your goal is to ship application-level event delivery.

Will SSE lose events after a disconnect?

Within retention and the 5,000-message per-resume cap, SSE catches up from the cursor. Beyond that boundary you receive msgmesh-resync and must rebuild state from your own business system. MsgMesh does not provide snapshots.

What happens when a Webhook fails?

Temporary failures retry automatically. Status codes that should not be retried go directly to the dead-letter queue. Exhausted deliveries also go there and can be inspected and replayed in the panel. Dead-letter applies only to Webhook delivery failures.

How are message operations billed?

Each 16 KiB, rounded up, is one size unit. Operations = size units × (one publish + each billable delivery). History queries and management operations do not count. Overage is off by default and is billed only after you add usable balance and explicitly enable it.

Can I use the public beta for production?

There is no SLA. MsgMesh currently runs in one Taiwanese facility with single-replica Kafka, so a disk or broker failure can permanently lose messages still inside retention. Use it for workloads that can tolerate and recover from that risk. Evaluate carefully before using it for payments, inventory or other core transactions.

10.Next steps

  • Quickstart — copyable SDK, curl and MCP snippets.
  • API reference ↗ — request and response shapes for every endpoint.
  • Live demo — no signup; publish an event and watch it arrive.
  • Official examples — full projects you can clone and run.
  • Pricing — Free at US$0 or Paid at US$19/month, with optional prepaid overage.

Questions or need integration help? 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.