This episode covers reliable message delivery: guarantees from at-most-once to exactly-once, the acknowledgement mechanism, offline message queues, persistence to databases and message brokers, and keeping messages ordered.

WebSocket only guarantees that a message reaches the network, not that it reaches its destination. If the client is offline when the message is sent, or the server crashes after receiving it, the message is simply lost. For critical applications such as fund transfers or orders, that loss is unacceptable.
Episode 14 covers reliability: guaranteeing messages are not lost or duplicated, and queuing: storing messages while the receiver is not ready. You will learn the delivery guarantee models, acknowledgement, offline queues, and persistence.
Message systems generally offer three guarantees at different costs.
at-most-once : pesan mungkin hilang, tidak pernah duplikat
at-least-once : pesan pasti sampai, mungkin duplikat
exactly-once : pasti sampai dan tidak duplikat (paling mahal)WebSocket's default is at-most-once: the message is sent once; if it fails, it is lost. For stronger guarantees, the application must add acknowledgement and retry on top of the protocol.
Choose the level that matches the consequences of losing a message in your application.
The client tells the server the message was received by sending back an ack.
wss.on("connection", (ws) => {
ws.on("message", (data) => {
const pesan = JSON.parse(data.toString());
simpanPesan(pesan.id, pesan);
ws.send(JSON.stringify({
type: "ack",
id: pesan.id,
}));
});
});simpanPesan(pesan.id, pesan) stores the message before the ack is sent. If the server crashes after the ack, the message is already saved; if before the ack, the client resends and the server detects the duplicate via the ID.
The sender must retry when the ack does not arrive, and the receiver must reject duplicates.
let idBerikutnya = 1;
const terack = new Set();
function kirimDenganAck(ws, pesan) {
const id = idBerikutnya++;
pesan.id = id;
ws.send(JSON.stringify(pesan));
setTimeout(() => {
if (!terack.has(id)) {
kirimDenganAck(ws, Object.assign({}, pesan));
}
}, 3000);
}terack.has(id) marks messages already confirmed by the receiver. The 3-second timer triggers a resend until the ack arrives, with an attempt limit to prevent endless sending.
When the receiver is offline, messages must be stored until they return.
const antreanOffline = new Map();
socket.on("message:send", (pesan) => {
const tujuan = penggunaOnline(pesan.to);
if (tujuan) {
tujuan.emit("message:new", pesan);
} else {
const q = antreanOffline.get(pesan.to) || [];
q.push(pesan);
antreanOffline.set(pesan.to, q);
}
});
socket.on("presence:online", (userId) => {
const q = antreanOffline.get(userId) || [];
q.forEach((pesan) => socket.emit("message:new", pesan));
antreanOffline.delete(userId);
});antreanOffline stores messages per user. When the user comes back online, the whole queue is sent and cleared.
Not all messages are equally important. A priority queue sends important messages first.
const antrean = [];
function tambah(pesan, prioritas) {
antrean.push({ pesan, prioritas });
antrean.sort((a, b) => b.prioritas - a.prioritas);
}antrean.sort(...) keeps the order by priority. System alerts are always picked up before promo notifications.
const tersimpan = await db.pesan.create({
data: {
id: pesan.id,
dari: pesan.dari,
ke: pesan.ke,
teks: pesan.teks,
},
});Storing in a database makes messages survive restarts. For high throughput, a message broker such as Redis or RabbitMQ handles queues with battle-tested delivery guarantees. The Redis list structure and Pub/Sub patterns will be covered more deeply in episode 16.
The network does not guarantee messages arrive in order. Add a sequence number so the receiver can detect swapped or lost messages.
let diharapkan = 1;
function proses(pesan) {
if (pesan.seq !== diharapkan) {
socket.emit("sync:request", { mulai: diharapkan });
return;
}
tampilkan(pesan);
diharapkan = pesan.seq + 1;
}pesan.seq !== diharapkan signals that a message was missed or arrived out of order. This pattern is known as gap detection: the client requests a resync only from the missing number, then resumes processing. It saves bandwidth compared to resending the whole history.
Episode 14 equipped WebSocket with the reliability the protocol does not provide out of the box: a delivery guarantee chosen to fit the need, acks preventing loss, queues holding messages for offline receivers, and persistence making messages survive restarts.
Key takeaways:
In the next episode we move into the scaling phase: load balancing WebSocket connections — sticky sessions, Layer 4 and Layer 7 load balancers, and health checks for zero-downtime deployments.