diff --git a/web/chat/chat.react.js b/web/chat/chat.react.js
--- a/web/chat/chat.react.js
+++ b/web/chat/chat.react.js
@@ -2,12 +2,16 @@
 
 import * as React from 'react';
 
+import ThreadDraftUpdater from 'lib/components/thread-draft-updater.react';
+import { isLoggedIn } from 'lib/selectors/user-selectors';
+
 import { useSelector } from '../redux/redux-utils';
 import ChatMessageListContainer from './chat-message-list-container.react';
 import ChatTabs from './chat-tabs.react';
 import { ThreadListProvider } from './thread-list-provider';
 
 function Chat(): React.Node {
+  const loggedIn = useSelector(isLoggedIn);
   const activeChatThreadID = useSelector(
     state => state.navInfo.activeChatThreadID,
   );
@@ -26,10 +30,15 @@
     return <ChatMessageListContainer activeChatThreadID={activeChatThreadID} />;
   }, [activeChatThreadID]);
 
+  let threadDraftUpdater = null;
+  if (loggedIn) {
+    threadDraftUpdater = <ThreadDraftUpdater />;
+  }
   return (
     <>
       {chatList}
       {messageList}
+      {threadDraftUpdater}
     </>
   );
 }
diff --git a/web/input/input-state-container.react.js b/web/input/input-state-container.react.js
--- a/web/input/input-state-container.react.js
+++ b/web/input/input-state-container.react.js
@@ -37,6 +37,7 @@
 } from 'lib/shared/message-utils';
 import {
   createRealThreadFromPendingThread,
+  draftKeyFromThreadID,
   threadIsPending,
 } from 'lib/shared/thread-utils';
 import type { CalendarQuery } from 'lib/types/entry-types';
@@ -84,6 +85,7 @@
 type Props = {
   ...BaseProps,
   +activeChatThreadID: ?string,
+  +drafts: { +[key: string]: string },
   +viewerID: ?string,
   +messageStoreMessages: { +[id: string]: RawMessageInfo },
   +exifRotate: boolean,
@@ -117,13 +119,16 @@
   +pendingUploads: {
     [threadID: string]: { [localUploadID: string]: PendingMultimediaUpload },
   },
-  +drafts: { [threadID: string]: string },
   +textCursorPositions: { [threadID: string]: number },
 };
+
+type PropsAndState = {
+  ...Props,
+  ...State,
+};
 class InputStateContainer extends React.PureComponent<Props, State> {
   state: State = {
     pendingUploads: {},
-    drafts: {},
     textCursorPositions: {},
   };
   replyCallbacks: Array<(message: string) => void> = [];
@@ -147,10 +152,6 @@
   }
 
   static getDerivedStateFromProps(props: Props, state: State) {
-    const drafts = InputStateContainer.reassignToRealizedThreads(
-      state.drafts,
-      props,
-    );
     const pendingUploads = InputStateContainer.reassignToRealizedThreads(
       state.pendingUploads,
       props,
@@ -160,14 +161,11 @@
       props,
     );
 
-    if (!drafts && !pendingUploads && !textCursorPositions) {
+    if (!pendingUploads && !textCursorPositions) {
       return null;
     }
 
     const stateUpdate = {};
-    if (drafts) {
-      stateUpdate.drafts = drafts;
-    }
     if (pendingUploads) {
       stateUpdate.pendingUploads = pendingUploads;
     }
@@ -458,9 +456,11 @@
 
   inputStateSelector = _memoize((threadID: string) =>
     createSelector(
-      (state: State) => state.pendingUploads[threadID],
-      (state: State) => state.drafts[threadID],
-      (state: State) => state.textCursorPositions[threadID],
+      (propsAndState: PropsAndState) => propsAndState.pendingUploads[threadID],
+      (propsAndState: PropsAndState) =>
+        propsAndState.drafts[draftKeyFromThreadID(threadID)],
+      (propsAndState: PropsAndState) =>
+        propsAndState.textCursorPositions[threadID],
       (
         pendingUploads: ?{ [localUploadID: string]: PendingMultimediaUpload },
         draft: ?string,
@@ -1069,14 +1069,13 @@
   }
 
   setDraft(threadID: string, draft: string) {
-    this.setState(prevState => {
-      const newThreadID = this.getRealizedOrPendingThreadID(threadID);
-      return {
-        drafts: {
-          ...prevState.drafts,
-          [newThreadID]: draft,
-        },
-      };
+    const newThreadID = this.getRealizedOrPendingThreadID(threadID);
+    this.props.dispatch({
+      type: 'UPDATE_DRAFT',
+      payload: {
+        key: draftKeyFromThreadID(newThreadID),
+        text: draft,
+      },
     });
   }
 
@@ -1238,7 +1237,10 @@
   render() {
     const { activeChatThreadID } = this.props;
     const inputState = activeChatThreadID
-      ? this.inputStateSelector(activeChatThreadID)(this.state)
+      ? this.inputStateSelector(activeChatThreadID)({
+          ...this.state,
+          ...this.props,
+        })
       : null;
     return (
       <InputStateContext.Provider value={inputState}>
@@ -1259,6 +1261,7 @@
     const activeChatThreadID = useSelector(
       state => state.navInfo.activeChatThreadID,
     );
+    const drafts = useSelector(state => state.draftStore.drafts);
     const viewerID = useSelector(
       state => state.currentUserInfo && state.currentUserInfo.id,
     );
@@ -1299,6 +1302,7 @@
       <InputStateContainer
         {...props}
         activeChatThreadID={activeChatThreadID}
+        drafts={drafts}
         viewerID={viewerID}
         messageStoreMessages={messageStoreMessages}
         exifRotate={exifRotate}
diff --git a/web/root.js b/web/root.js
--- a/web/root.js
+++ b/web/root.js
@@ -18,7 +18,7 @@
 const persistConfig = {
   key: 'root',
   storage,
-  whitelist: ['enabledApps', 'deviceID'],
+  whitelist: ['enabledApps', 'deviceID', 'draftStore'],
   version: 0,
 };