diff --git a/lib/reducers/dm-operations-queue-reducer.js b/lib/reducers/dm-operations-queue-reducer.js
--- a/lib/reducers/dm-operations-queue-reducer.js
+++ b/lib/reducers/dm-operations-queue-reducer.js
@@ -1,6 +1,9 @@
 // @flow
 
+import _mapValues from 'lodash/fp/mapValues.js';
+
 import {
+  pruneDMOpsQueueActionType,
   type QueuedDMOperations,
   queueDMOpsActionType,
 } from '../types/dm-ops.js';
@@ -11,14 +14,24 @@
   action: BaseAction,
 ): QueuedDMOperations {
   if (action.type === queueDMOpsActionType) {
-    const { threadID, operation } = action.payload;
+    const { threadID, operation, timestamp } = action.payload;
     return {
       ...store,
       operations: {
         ...store.operations,
-        [threadID]: [...(store.operations[threadID] ?? []), operation],
+        [threadID]: [
+          ...(store.operations[threadID] ?? []),
+          { operation, timestamp },
+        ],
       },
     };
+  } else if (action.type === pruneDMOpsQueueActionType) {
+    return {
+      ...store,
+      operations: _mapValues(operations =>
+        operations.filter(op => op.timestamp >= action.payload.pruneMaxTime),
+      )(store.operations),
+    };
   }
   return store;
 }
diff --git a/lib/shared/dm-ops/dm-ops-queue-pruner.react.js b/lib/shared/dm-ops/dm-ops-queue-pruner.react.js
new file mode 100644
--- /dev/null
+++ b/lib/shared/dm-ops/dm-ops-queue-pruner.react.js
@@ -0,0 +1,29 @@
+// @flow
+import * as React from 'react';
+
+import { pruneDMOpsQueueActionType } from '../../types/dm-ops.js';
+import { useDispatch } from '../../utils/redux-utils.js';
+
+const PRUNING_FREQUENCY = 6 * 60 * 60 * 1000;
+const MAX_AGE_PRUNED = 3 * 24 * 60 * 60 * 1000;
+
+function DmOpsQueuePruner(): React.Node {
+  const dispatch = useDispatch();
+  React.useEffect(() => {
+    const intervalID = setInterval(() => {
+      const now = Date.now();
+      dispatch({
+        type: pruneDMOpsQueueActionType,
+        payload: {
+          pruneMaxTime: now - MAX_AGE_PRUNED,
+        },
+      });
+    }, PRUNING_FREQUENCY);
+
+    return () => clearInterval(intervalID);
+  }, [dispatch]);
+
+  return null;
+}
+
+export { DmOpsQueuePruner };
diff --git a/lib/tunnelbroker/tunnelbroker-context.js b/lib/tunnelbroker/tunnelbroker-context.js
--- a/lib/tunnelbroker/tunnelbroker-context.js
+++ b/lib/tunnelbroker/tunnelbroker-context.js
@@ -9,6 +9,7 @@
 import { PeerToPeerMessageHandler } from './peer-to-peer-message-handler.js';
 import type { SecondaryTunnelbrokerConnection } from './secondary-tunnelbroker-connection.js';
 import { tunnnelbrokerURL } from '../facts/tunnelbroker.js';
+import { DmOpsQueuePruner } from '../shared/dm-ops/dm-ops-queue-pruner.react.js';
 import { IdentityClientContext } from '../shared/identity-client-context.js';
 import { tunnelbrokerHeartbeatTimeout } from '../shared/timeouts.js';
 import { isWebPlatform } from '../types/device-types.js';
@@ -461,6 +462,7 @@
         socketSend={socketSend}
       />
       <PeerToPeerProvider>{children}</PeerToPeerProvider>
+      <DmOpsQueuePruner />
     </TunnelbrokerContext.Provider>
   );
 }
diff --git a/lib/types/dm-ops.js b/lib/types/dm-ops.js
--- a/lib/types/dm-ops.js
+++ b/lib/types/dm-ops.js
@@ -313,6 +313,12 @@
 export type QueueDMOpsPayload = {
   +operation: DMOperation,
   +threadID: string,
+  +timestamp: number,
+};
+
+export const pruneDMOpsQueueActionType = 'PRUNE_DM_OPS_QUEUE';
+export type PruneDMOpsQueuePayload = {
+  +pruneMaxTime: number,
 };
 
 export const scheduleP2PMessagesActionType = 'SCHEDULE_P2P_MESSAGES';
@@ -323,6 +329,9 @@
 
 export type QueuedDMOperations = {
   +operations: {
-    +[threadID: string]: $ReadOnlyArray<DMOperation>,
+    +[threadID: string]: $ReadOnlyArray<{
+      +operation: DMOperation,
+      +timestamp: number,
+    }>,
   },
 };
diff --git a/lib/types/redux-types.js b/lib/types/redux-types.js
--- a/lib/types/redux-types.js
+++ b/lib/types/redux-types.js
@@ -45,6 +45,7 @@
   ProcessDMOpsPayload,
   QueuedDMOperations,
   QueueDMOpsPayload,
+  PruneDMOpsQueuePayload,
 } from './dm-ops.js';
 import type { DraftStore } from './draft-types.js';
 import type { EnabledApps, SupportedApps } from './enabled-apps.js';
@@ -1581,7 +1582,8 @@
           +deviceToken: string,
         },
       }
-    | { +type: 'QUEUE_DM_OPS', +payload: QueueDMOpsPayload },
+    | { +type: 'QUEUE_DM_OPS', +payload: QueueDMOpsPayload }
+    | { +type: 'PRUNE_DM_OPS_QUEUE', +payload: PruneDMOpsQueuePayload },
 }>;
 
 export type ActionPayload = ?(Object | Array<*> | $ReadOnlyArray<*> | string);