diff --git a/keyserver/src/endpoints.js b/keyserver/src/endpoints.js
--- a/keyserver/src/endpoints.js
+++ b/keyserver/src/endpoints.js
@@ -25,6 +25,7 @@
   messageFetchResponder,
   multimediaMessageCreationResponder,
   reactionMessageCreationResponder,
+  editMessageCreationResponder,
 } from './responders/message-responders.js';
 import { updateRelationshipsResponder } from './responders/relationship-responders.js';
 import {
@@ -87,6 +88,10 @@
     responder: reactionMessageCreationResponder,
     requiredPolicies: baseLegalPolicies,
   },
+  edit_message: {
+    responder: editMessageCreationResponder,
+    requiredPolicies: baseLegalPolicies,
+  },
   create_report: {
     responder: reportCreationResponder,
     requiredPolicies: [],
diff --git a/keyserver/src/responders/message-responders.js b/keyserver/src/responders/message-responders.js
--- a/keyserver/src/responders/message-responders.js
+++ b/keyserver/src/responders/message-responders.js
@@ -15,11 +15,14 @@
   type SendTextMessageRequest,
   type SendMultimediaMessageRequest,
   type SendReactionMessageRequest,
+  type SendEditMessageRequest,
   type FetchMessageInfosResponse,
   type FetchMessageInfosRequest,
   defaultNumberPerThread,
   type SendMessageResponse,
+  type SendEditMessageResponse,
 } from 'lib/types/message-types.js';
+import type { EditMessageData } from 'lib/types/messages/edit.js';
 import type { ReactionMessageData } from 'lib/types/messages/reaction.js';
 import type { TextMessageData } from 'lib/types/messages/text.js';
 import { threadPermissions } from 'lib/types/thread-types.js';
@@ -286,9 +289,73 @@
   return { newMessageInfo: rawMessageInfos[0] };
 }
 
+const editMessageRequestInputValidator = tShape({
+  targetMessageID: t.String,
+  text: t.String,
+});
+async function editMessageCreationResponder(
+  viewer: Viewer,
+  input: any,
+): Promise<SendEditMessageResponse> {
+  const request: SendEditMessageRequest = input;
+  await validateInput(viewer, editMessageRequestInputValidator, input);
+
+  const { targetMessageID, text: rawText } = request;
+  const text = trimMessage(rawText);
+  if (!targetMessageID || !text) {
+    throw new ServerError('invalid_parameters');
+  }
+
+  const targetMessageInfo = await fetchMessageInfoByID(viewer, targetMessageID);
+  if (!targetMessageInfo || !targetMessageInfo.id) {
+    throw new ServerError('invalid_parameters');
+  }
+
+  if (targetMessageInfo.type !== messageTypes.TEXT) {
+    throw new ServerError('invalid_parameters');
+  }
+
+  const { threadID } = targetMessageInfo;
+  const [serverThreadInfos, hasPermission] = await Promise.all([
+    fetchServerThreadInfos(SQL`t.id = ${threadID}`),
+    checkThreadPermission(viewer, threadID, threadPermissions.EDIT_MESSAGE),
+  ]);
+
+  const targetMessageThreadInfo = serverThreadInfos.threadInfos[threadID];
+  if (targetMessageThreadInfo.sourceMessageID === targetMessageID) {
+    // We are editing first message of the sidebar
+    // If client wants to do that it sends id of the sourceMessage instead
+    throw new ServerError('invalid_parameters');
+  }
+
+  if (!hasPermission) {
+    throw new ServerError('invalid_parameters');
+  }
+
+  if (targetMessageInfo.creatorID !== viewer.id) {
+    throw new ServerError('invalid_parameters');
+  }
+
+  const messagesData = [];
+  const messageData: EditMessageData = {
+    type: messageTypes.EDIT_MESSAGE,
+    threadID,
+    creatorID: viewer.id,
+    time: Date.now(),
+    targetMessageID,
+    text,
+  };
+  messagesData.push(messageData);
+
+  const newMessageInfos = await createMessages(viewer, messagesData);
+
+  return { newMessageInfos };
+}
+
 export {
   textMessageCreationResponder,
   messageFetchResponder,
   multimediaMessageCreationResponder,
   reactionMessageCreationResponder,
+  editMessageCreationResponder,
 };
diff --git a/lib/types/endpoints.js b/lib/types/endpoints.js
--- a/lib/types/endpoints.js
+++ b/lib/types/endpoints.js
@@ -52,6 +52,7 @@
   CREATE_MESSAGE_REPORT: 'create_message_report',
   CREATE_MULTIMEDIA_MESSAGE: 'create_multimedia_message',
   CREATE_REACTION_MESSAGE: 'create_reaction_message',
+  EDIT_MESSAGE: 'edit_message',
   CREATE_TEXT_MESSAGE: 'create_text_message',
   CREATE_THREAD: 'create_thread',
   DELETE_ENTRY: 'delete_entry',
diff --git a/lib/types/message-types.js b/lib/types/message-types.js
--- a/lib/types/message-types.js
+++ b/lib/types/message-types.js
@@ -563,6 +563,15 @@
   +action: 'add_reaction' | 'remove_reaction',
 };
 
+export type SendEditMessageRequest = {
+  +targetMessageID: string,
+  +text: string,
+};
+
+export type SendEditMessageResponse = {
+  +newMessageInfos: $ReadOnlyArray<RawMessageInfo>,
+};
+
 // Used for the message info included in log-in type actions
 export type GenericMessagesResult = {
   +messageInfos: RawMessageInfo[],