The queue that never loses a message
The promise is simple: a message you sent arrives, even if the connection dropped between your thumb and the other person's screen. Keeping it is mostly a matter of being clear about which part of the system is the record and which part is merely the courier.
Each conversation has one Durable Object. One, globally, not one per datacentre. That is what turns a hard distributed problem into an ordinary one: there is a single authoritative place for a conversation to exist, so questions like "who is in this room" and "who should see this" have an answer rather than a consensus.
Clients hold a WebSocket to that object, using the hibernation API, so a room nobody is typing in costs nothing. Idle conversations are the overwhelming majority of conversations, and a design that charges for them by keeping a process warm scales exactly the wrong way.
Here is the part that carries the guarantee. Sending a message is a REST call that persists to the database first. Only after it is stored does the room broadcast it to the sockets. The socket is a notification, not a delivery mechanism, and the database is the record. This ordering is the whole thing, and it is worth being precise about why.
If the broadcast is the delivery, then a socket that is gone at that instant is a message that never happened, and you are into the business of retry queues, acknowledgements and duplicate suppression to get back to correctness. If persistence comes first, a disconnected client has not lost anything. It missed a notification. When it reconnects it reads the thread and the message is simply there, because it was there the whole time, in the place that holds threads.
The socket then only has to be good at the thing sockets are good at: making an arrival feel instant to somebody who is currently looking. When it fails, it degrades into a slower version of the same correct answer rather than into a wrong one.
The same object handles the ephemera. Typing indicators fan out client-to-client through the room without touching the database, which is correct, because a typing indicator has no value five seconds later and no business being durable.
The rule we would give to anybody building this: decide early which component is allowed to be the truth, and then never let anything else quietly become it. Almost every "lost message" bug we have read about, in our system and in other people's, is a moment where a transport was doing a database's job.