diff --git a/lib/types/sqlite-types.js b/lib/types/sqlite-types.js
--- a/lib/types/sqlite-types.js
+++ b/lib/types/sqlite-types.js
@@ -55,6 +55,11 @@
     timestampCursor: ?string,
     messageIDCursor: ?string,
   ) => Promise<Array<ClientDBMessageInfo>>,
+  +fetchMessages: (
+    threadID: string,
+    limit: number,
+    offset: number,
+  ) => Promise<Array<ClientDBMessageInfo>>,
 
   // write operations
   +removeInboundP2PMessages: (ids: $ReadOnlyArray<string>) => Promise<void>,
diff --git a/lib/utils/__mocks__/config.js b/lib/utils/__mocks__/config.js
--- a/lib/utils/__mocks__/config.js
+++ b/lib/utils/__mocks__/config.js
@@ -44,6 +44,7 @@
     getRelatedMessages: jest.fn(),
     getOutboundP2PMessagesByID: jest.fn(),
     searchMessages: jest.fn(),
+    fetchMessages: jest.fn(),
   },
 });
 
diff --git a/native/database/sqlite-api.js b/native/database/sqlite-api.js
--- a/native/database/sqlite-api.js
+++ b/native/database/sqlite-api.js
@@ -16,6 +16,7 @@
   getRelatedMessages: commCoreModule.getRelatedMessages,
   getOutboundP2PMessagesByID: commCoreModule.getOutboundP2PMessagesByID,
   searchMessages: commCoreModule.searchMessages,
+  fetchMessages: commCoreModule.fetchMessages,
 
   // write operations
   removeInboundP2PMessages: commCoreModule.removeInboundP2PMessages,
diff --git a/web/database/sqlite-api.js b/web/database/sqlite-api.js
--- a/web/database/sqlite-api.js
+++ b/web/database/sqlite-api.js
@@ -81,6 +81,23 @@
     return messages ? [...messages] : [];
   },
 
+  async fetchMessages(
+    threadID: string,
+    limit: number,
+    offset: number,
+  ): Promise<ClientDBMessageInfo[]> {
+    const sharedWorker = await getCommSharedWorker();
+
+    const data = await sharedWorker.schedule({
+      type: workerRequestMessageTypes.FETCH_MESSAGES,
+      threadID,
+      limit,
+      offset,
+    });
+    const messages: ?$ReadOnlyArray<ClientDBMessageInfo> = data?.messages;
+    return messages ? [...messages] : [];
+  },
+
   // write operations
   async removeInboundP2PMessages(ids: $ReadOnlyArray<string>): Promise<void> {
     const sharedWorker = await getCommSharedWorker();
diff --git a/web/shared-worker/worker/shared-worker.js b/web/shared-worker/worker/shared-worker.js
--- a/web/shared-worker/worker/shared-worker.js
+++ b/web/shared-worker/worker/shared-worker.js
@@ -281,6 +281,16 @@
       type: workerResponseMessageTypes.GET_MESSAGES,
       messages: webMessageEntities.map(webMessageToClientDBMessageInfo),
     };
+  } else if (message.type === workerRequestMessageTypes.FETCH_MESSAGES) {
+    const webMessageEntities = sqliteQueryExecutor.fetchMessagesWeb(
+      message.threadID,
+      message.limit,
+      message.offset,
+    );
+    return {
+      type: workerResponseMessageTypes.GET_MESSAGES,
+      messages: webMessageEntities.map(webMessageToClientDBMessageInfo),
+    };
   }
 
   // write operations
diff --git a/web/types/worker-types.js b/web/types/worker-types.js
--- a/web/types/worker-types.js
+++ b/web/types/worker-types.js
@@ -48,6 +48,7 @@
   GET_OUTBOUND_P2P_MESSAGES_BY_ID: 22,
   SEARCH_MESSAGES: 23,
   RESET_OUTBOUND_P2P_MESSAGES: 24,
+  FETCH_MESSAGES: 25,
 });
 
 export const workerWriteRequests: $ReadOnlyArray<number> = [
@@ -217,6 +218,13 @@
   +deviceID: string,
 };
 
+export type FetchMessagesRequestMessage = {
+  +type: 25,
+  +threadID: string,
+  +limit: number,
+  +offset: number,
+};
+
 export type WorkerRequestMessage =
   | PingWorkerRequestMessage
   | InitWorkerRequestMessage
@@ -242,7 +250,8 @@
   | GetRelatedMessagesRequestMessage
   | GetOutboundP2PMessagesByIDRequestMessage
   | SearchMessagesRequestMessage
-  | ResetOutboundP2PMessagesRequestMessage;
+  | ResetOutboundP2PMessagesRequestMessage
+  | FetchMessagesRequestMessage;
 
 export type WorkerRequestProxyMessage = {
   +id: number,