Three layers behind “no lost or duplicated messages”
Reliable delivery is not a slogan. It is three mechanisms backing each other up: persist-then-acknowledge, per-conversation sequence with backfill, and offline push as the last resort.
Lost and duplicated messages are the two complaints that are hardest to reproduce. They almost always happen in the same place: flaky networks, network switching, reconnection.
Staying correct in that window needs three mechanisms at once.
Layer one: persist first, then acknowledge
After a client sends a message, the server must persist it to multiple replicas before returning success. Acknowledge-then-persist means a node failure leaves a message the user believes was sent and the peer never receives.
Layer two: sequence numbers and backfill
Each conversation keeps a monotonically increasing sequence. Clients detect gaps and request the missing range; duplicates are dropped. This repairs both messages sent during an outage and duplicates after reconnection, without complex client-side state.
Layer three: offline push as fallback
When the long connection is unavailable, push is the only way to reach the user. Its job is to notify, not to carry the message: opening the app still runs the backfill flow, so a dropped push does not lose data.
Why all three
Persist only: messages during an outage are never filled in. Sequence only: a killed app never learns there is something new. Push only: push itself is unreliable. Together they close the loop.
A single question tells you whether an IM stack is serious: after reconnection, how does the client know which range to backfill?