# Messages queued server-side during disconnect and delivered in order on reconnection

- **Pattern:** `ab-000743` (`community-realtime.message-delivery.offline-queue-on-reconnect`)
- **Severity:** medium
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000743
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000743)

## Why it matters

A user who loses connectivity for 30 seconds and reconnects finds the conversation with a gap — every message sent during their outage is gone with no indication it was missed. This is especially damaging in moderation workflows, incident channels, or any context where missing a message has operational consequences. ISO 25010 reliability specifically covers data persistence across failure events; dropping messages on disconnect is a reliability failure regardless of how brief the outage.

## Severity rationale

Medium because messages sent to disconnected users are permanently lost without a server-side queue, creating silent data gaps on reconnect.

## Remediation

Persist messages for offline users and flush the queue in delivery order on reconnect. For production use, back the queue with Redis lists rather than in-process Maps.

```ts
// On connect, deliver queued messages in order
socket.on('connect_post_auth', async () => {
  const queued = await redis.lrange(`offline:${socket.userId}`, 0, -1);
  for (const raw of queued) {
    socket.emit('message', JSON.parse(raw));
  }
  await redis.del(`offline:${socket.userId}`);
});

// When a message is sent, queue for offline subscribers
const offlineUsers = await getOfflineChannelMembers(channel);
for (const uid of offlineUsers) {
  await redis.rpush(`offline:${uid}`, JSON.stringify(message));
  await redis.expire(`offline:${uid}`, 86400); // 24 h TTL
}
```

Set a TTL on the queue to prevent unbounded growth for users who never reconnect.

## Detection

- **ID:** `offline-queue-on-reconnect`
- **Severity:** `medium`
- **What to look for:** Count all offline message handling paths. Enumerate queueing mechanisms: database writes, in-memory cache, Redis lists, or message broker queues. Check that delivery order is preserved (messages delivered in the same order they were queued).
- **Pass criteria:** Messages sent while a user is offline are queued via at least 1 persistence mechanism and delivered in order when the user reconnects. The queue must preserve monotonic ordering.
- **Fail criteria:** Messages sent during disconnection are lost, or no queueing mechanism exists.
- **Skip (N/A) when:** Never — message queueing on disconnect is essential for reliability.
- **Detail on fail:** `"No offline message queue. Messages sent while a user is disconnected are lost."`
- **Remediation:** Queue messages for offline users and deliver on reconnect:

  ```ts
  interface QueuedMessage {
    id: string;
    userId: string;
    channel: string;
    content: string;
    timestamp: number;
  }

  const messageQueue = new Map<string, QueuedMessage[]>();

  socket.on('send_message', async (data) => {
    const message = { id: nanoid(), userId: socket.userId, channel: data.channel, content: data.content, timestamp: Date.now() };

    // Broadcast to online users
    io.to(data.channel).emit('message', message);

    // Queue for offline users (simplified example)
    const users = await getChannelSubscribers(data.channel);
    for (const user of users) {
      if (!isUserOnline(user.id)) {
        if (!messageQueue.has(user.id)) {
          messageQueue.set(user.id, []);
        }
        messageQueue.get(user.id)!.push(message);
      }
    }
  });

  socket.on('reconnect', () => {
    const queued = messageQueue.get(socket.userId) || [];
    for (const msg of queued) {
      socket.emit('message', msg);
    }
    messageQueue.delete(socket.userId);
  });
  ```

## External references

- iso-25010 reliability.recoverability

Taxons: error-resilience, data-integrity

HTML version: https://auditbuffet.com/patterns/ab-000743
