> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/ably/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Message History

Ably's message history feature allows clients to retrieve messages they missed during a brief disconnection. Using it as a primary database is an architectural anti-pattern that will lead to problems.

When a client disconnects, Ably automatically handles the reconnection. If the disconnection lasts less than 2 minutes, Ably's connection-state recovery feature streams any missed messages to the client. For longer outages, the client must use the History API to fetch the missed messages.

<Aside data-type="important">
  History is not a database. The feature is purely for time-based retrieval on a single channel. For complex queries or long-term data persistence, you must use a dedicated database. See [integrations](/docs/platform/integrations) for best practices.
</Aside>

## Retrieve channel history

Use the [`history()`](/docs/api/realtime-sdk/history#channel-history) method to retrieve previously published messages:

<Code>
  ```realtime_javascript theme={null}
  const channel = realtime.channels.get('my-channel');
  const history = await channel.history();

  console.log(`Retrieved ${history.items.length} messages`);
  history.items.forEach((message) => {
    console.log(`${message.name}: ${message.data}`);
  });
  ```

  ```realtime_nodejs theme={null}
  const channel = realtime.channels.get('my-channel');
  const history = await channel.history();

  console.log(`Retrieved ${history.items.length} messages`);
  history.items.forEach((message) => {
    console.log(`${message.name}: ${message.data}`);
  });
  ```

  ```realtime_java theme={null}
  Channel channel = realtime.channels.get("my-channel");
  PaginatedResult<Message> history = channel.history(null);

  System.out.println("Retrieved " + history.items().length + " messages");
  for (Message message : history.items()) {
      System.out.println(message.name + ": " + message.data);
  }
  ```

  ```realtime_csharp theme={null}
  IRealtimeChannel channel = realtime.Channels.Get("my-channel");
  PaginatedResult<Message> history = await channel.HistoryAsync();

  Console.WriteLine($"Retrieved {history.Items.Count} messages");
  foreach (var message in history.Items) {
      Console.WriteLine($"{message.Name}: {message.Data}");
  }
  ```

  ```realtime_python theme={null}
  channel = realtime.channels.get('my-channel')
  history = await channel.history()

  print(f'Retrieved {len(history.items)} messages')
  for message in history.items:
      print(f'{message.name}: {message.data}')
  ```

  ```rest_javascript theme={null}
  const channel = rest.channels.get('my-channel');
  const history = await channel.history();

  console.log(`Retrieved ${history.items.length} messages`);
  history.items.forEach((message) => {
    console.log(`${message.name}: ${message.data}`);
  });
  ```
</Code>

## History parameters

Customize your history query with the following parameters:

| Parameter     | Description                                                                 |
| ------------- | --------------------------------------------------------------------------- |
| **start**     | Earliest time in milliseconds since the epoch for any messages retrieved    |
| **end**       | Latest time in milliseconds since the epoch for any messages retrieved      |
| **direction** | `forwards` or `backwards` (default: `backwards`)                            |
| **limit**     | Maximum number of messages to retrieve per page, up to 1,000 (default: 100) |

### Query by time range

<Code>
  ```javascript theme={null}
  const oneDayAgo = Date.now() - (24 * 60 * 60 * 1000);

  const history = await channel.history({
    start: oneDayAgo,
    end: Date.now(),
    limit: 50
  });
  ```

  ```python theme={null}
  import time

  one_day_ago = int((time.time() - (24 * 60 * 60)) * 1000)

  history = await channel.history(
      start=one_day_ago,
      end=int(time.time() * 1000),
      limit=50
  )
  ```
</Code>

### Get messages in chronological order

<Code>
  ```javascript theme={null}
  const history = await channel.history({
    direction: 'forwards',
    limit: 100
  });
  ```
</Code>

## Pagination

History results are paginated. Use the `hasNext()` and `next()` methods to iterate through pages:

<Code>
  ```javascript theme={null}
  let history = await channel.history({ limit: 100 });

  while (history) {
    console.log(`Page has ${history.items.length} messages`);
    
    // Process messages
    history.items.forEach((message) => {
      console.log(message.data);
    });
    
    // Get next page
    if (history.hasNext()) {
      history = await history.next();
    } else {
      history = null;
    }
  }
  ```

  ```java theme={null}
  PaginatedResult<Message> history = channel.history(null);

  do {
      System.out.println("Page has " + history.items().length + " messages");
      
      for (Message message : history.items()) {
          System.out.println(message.data);
      }
      
      if (history.hasNext()) {
          history = history.next();
      } else {
          break;
      }
  } while (true);
  ```
</Code>

## Continuous history with rewind

Use the [rewind channel option](/docs/channels/options/rewind) to get historical messages when attaching to a channel:

<Code>
  ```javascript theme={null}
  const channel = realtime.channels.get('my-channel', {
    params: { rewind: '10' }  // Get last 10 messages
  });

  await channel.subscribe((message) => {
    console.log('Received:', message.data);
  });
  ```

  ```python theme={null}
  channel = realtime.channels.get(
      'my-channel',
      channel_options={'params': {'rewind': '10'}}
  )

  def listener(message):
      print('Received:', message.data)

  await channel.subscribe(listener)
  ```
</Code>

### Rewind by time

You can also rewind by time interval:

<Code>
  ```javascript theme={null}
  // Rewind 5 minutes
  const channel = realtime.channels.get('my-channel', {
    params: { rewind: '5m' }
  });

  // Rewind 30 seconds
  const channel = realtime.channels.get('my-channel', {
    params: { rewind: '30s' }
  });
  ```
</Code>

## History with untilAttach

Get continuous history up to the point of channel attachment:

<Code>
  ```javascript theme={null}
  const channel = realtime.channels.get('my-channel');

  // Subscribe first to ensure no gap
  await channel.subscribe((message) => {
    console.log('Realtime:', message.data);
  });

  // Wait for attachment
  await channel.whenState('attached');

  // Get history up to attachment point
  const history = await channel.history({ untilAttach: true });
  console.log(`Got ${history.items.length} historical messages`);
  ```
</Code>

## Message retention

How long messages are stored depends on your [channel rules](/docs/pubsub/channels/overview#channel-rules):

| Storage Type         | Default   | Maximum   |
| -------------------- | --------- | --------- |
| **Ephemeral**        | 2 minutes | 2 minutes |
| **Persisted (Free)** | 24 hours  | 24 hours  |
| **Persisted (Paid)** | 72 hours  | 365 days  |

To enable longer retention, configure the "Persist all messages" [channel rule](/docs/channels#rules).

## Presence history

You can also retrieve historical presence events:

<Code>
  ```javascript theme={null}
  const history = await channel.presence.history();

  history.items.forEach((event) => {
    console.log(`${event.clientId} ${event.action} at ${new Date(event.timestamp)}`);
  });
  ```
</Code>

## Best practices

### Use history for catch-up only

History is designed for short-term catch-up, not as a primary data store:

<Code>
  ```javascript theme={null}
  // Good - catch up after reconnection
  realtime.connection.on('connected', async () => {
    const lastSync = localStorage.getItem('lastSync');
    if (lastSync) {
      const history = await channel.history({
        start: parseInt(lastSync),
        limit: 100
      });
      // Process missed messages
    }
  });

  // Avoid - using history as primary storage
  const getAllMessages = async () => {
    // Don't do this - use a database instead
    let allMessages = [];
    let history = await channel.history();
    while (history) {
      allMessages.push(...history.items);
      if (history.hasNext()) {
        history = await history.next();
      } else {
        break;
      }
    }
    return allMessages;
  };
  ```
</Code>

### Limit history queries

Be specific about what you need:

<Code>
  ```javascript theme={null}
  // Good - specific time range and limit
  const history = await channel.history({
    start: Date.now() - (60 * 60 * 1000), // Last hour
    limit: 50
  });

  // Avoid - unlimited query
  const history = await channel.history(); // Gets all available history
  ```
</Code>

### Use integrations for long-term storage

For long-term storage and complex queries, use [integrations](/docs/platform/integrations) to send data to your own database:

* Store messages in your database via webhooks
* Use message queues for processing
* Enable data warehousing for analytics

## Next steps

* Learn about [message storage](/docs/pubsub/storage) options
* Explore [rewind channel option](/docs/channels/options/rewind)
* Set up [integrations](/docs/platform/integrations) for long-term storage
* Understand [connection recovery](/docs/connect/states)
