Message Archive Management (MAM)
Message Archive Management (MAM), defined in XEP-0313, allows clients to query and retrieve archived messages from the server. This enables features like message history, search, and synchronization across devices.
Overview
MAM provides a standardized way to:
- Query archived messages based on various criteria (time range, JID, etc.)
- Retrieve message history
- Synchronize messages across multiple devices
- Search through past conversations
Querying the Archive
The MAM.queryArchive method allows you to query archived messages:
final mam = MAM();
final result = await MAM.queryArchive(
whixp.transport,
filter: mam.createFilter(
start: '2024-01-01T00:00:00Z',
end: '2024-01-31T23:59:59Z',
wth: 'user@example.com', // 'with' - filter by JID
),
pagination: RSMSet(max: 50),
);
Creating Filters
Filters allow you to narrow down the query results:
final mam = MAM();
// Filter by time range
final filter1 = mam.createFilter(
start: '2024-01-01T00:00:00Z',
end: '2024-01-31T23:59:59Z',
);
// Filter by JID
final filter2 = mam.createFilter(
wth: 'user@example.com',
);
// Filter by message IDs
final filter3 = mam.createFilter(
ids: ['msg-id-1', 'msg-id-2'],
);
// Filter with multiple criteria
final filter4 = mam.createFilter(
start: '2024-01-01T00:00:00Z',
wth: 'user@example.com',
includeGroupchats: true,
);
Filter Parameters
start: ISO 8601 timestamp for the start of the time rangeend: ISO 8601 timestamp for the end of the time rangewth: JID to filter messages (corresponds to 'with' - messages to/from this JID)beforeID: Message ID to start querying beforeafterID: Message ID to start querying afterids: List of specific message IDs to retrieveincludeGroupchats: Whether to include groupchat messages
Pagination
Use Result Set Management (RSM) for pagination:
final result = await MAM.queryArchive(
whixp.transport,
pagination: RSMSet(
max: 25, // Number of results per page
after: 'last-message-id', // Start after this message ID
),
);
Processing Results
MAM results are returned as messages containing MAMResult payloads:
whixp.addEventHandler<Message>('message', (message) {
if (message == null) return;
final results = message.get<MAMResult>();
if (results.isEmpty) return;
for (final result in results) {
final forwarded = result.forwarded;
if (forwarded?.actual is Message) {
final archivedMessage = forwarded!.actual as Message;
print('From: ${archivedMessage.from}');
print('To: ${archivedMessage.to}');
print('Body: ${archivedMessage.body}');
print('Timestamp: ${forwarded.delay?.stamp}');
}
}
});
Complete Example
Here's a complete example of querying and processing archived messages:
Future<void> queryMessages() async {
final mam = MAM();
final result = await MAM.queryArchive(
whixp.transport,
filter: mam.createFilter(
wth: 'user@example.com',
),
pagination: RSMSet(max: 25),
flipPage: true, // Reverse order (newest first)
failureCallback: (error) {
print('MAM query failed: ${error.reason}');
},
);
if (result.type == 'error' || result.error != null) {
print('Error querying archive');
return;
}
final fin = result.payload is MAMFin
? result.payload as MAMFin
: null;
if (fin != null) {
print('Complete: ${fin.complete}');
print('First: ${fin.first}');
print('Last: ${fin.last}');
}
}
// Handle incoming MAM results
whixp.addEventHandler<Message>('message', (message) {
final results = message?.get<MAMResult>();
if (results?.isEmpty ?? true) return;
for (final result in results!) {
final forwarded = result.forwarded;
final actual = forwarded?.actual;
if (actual is Message) {
print('Archived message: ${actual.body}');
print('From: ${actual.from}');
print('Timestamp: ${forwarded?.delay?.stamp}');
}
}
});
Pagination Example
To retrieve all messages using pagination:
Future<void> getAllMessages(String? lastItem) async {
final mam = MAM();
final result = await MAM.queryArchive(
whixp.transport,
filter: mam.createFilter(
wth: 'user@example.com',
),
pagination: RSMSet(
max: 25,
before: lastItem ?? '',
),
flipPage: true,
);
final fin = result.payload is MAMFin
? result.payload as MAMFin
: null;
if (fin == null || fin.complete) {
return; // All messages retrieved
}
final nextItem = fin.last?.lastItem;
if (nextItem != null) {
// Continue pagination
await getAllMessages(nextItem);
}
}
Retrieving Metadata
You can retrieve metadata about the archive:
final result = await MAM.retrieveMetadata(
transport: whixp.transport,
callback: (iq) {
// Process metadata
print('Metadata retrieved');
},
);
Best Practices
- Use pagination: Always use RSM pagination for large result sets to avoid overwhelming the client
- Filter appropriately: Use filters to narrow down results and reduce server load
- Handle errors: Always provide error callbacks for failed queries
- Process asynchronously: MAM queries can return many messages - process them asynchronously
- Cache results: Consider caching retrieved messages locally to reduce server queries
MAM is essential for building modern messaging applications with message history and synchronization capabilities.