diff --git a/keyserver/src/creators/olm-session-creator.js b/keyserver/src/creators/olm-session-creator.js
new file mode 100644
--- /dev/null
+++ b/keyserver/src/creators/olm-session-creator.js
@@ -0,0 +1,43 @@
+// @flow
+
+import type { Account as OlmAccount } from '@commapp/olm';
+
+import { ServerError } from 'lib/utils/errors.js';
+
+import { dbQuery, SQL } from '../database/database.js';
+import { fetchCallUpdateOlmAccount } from '../updaters/olm-account-updater.js';
+import { createPickledOlmSession } from '../utils/olm-utils.js';
+
+async function createOlmSession(
+  initialEncryptedMessage: string,
+  olmSessionType: 'content' | 'notifications',
+  cookieID: string,
+): Promise<void> {
+  const callback = (account: OlmAccount, picklingKey: string) =>
+    createPickledOlmSession(account, picklingKey, initialEncryptedMessage);
+
+  let pickledOlmSession;
+  try {
+    pickledOlmSession = await fetchCallUpdateOlmAccount(
+      olmSessionType,
+      callback,
+    );
+  } catch (e) {
+    throw new ServerError('olm_session_creation_failure');
+  }
+
+  const isContent = olmSessionType === 'content';
+  // We match the native client behavior here where olm session is overwritten
+  // in case it is initialized twice for the same pair of identities
+  await dbQuery(
+    SQL`
+      INSERT INTO olm_sessions (cookie_id, is_content, 
+        version, pickled_olm_session)
+      VALUES (${cookieID}, ${isContent}, 0, ${pickledOlmSession})
+      ON DUPLICATE KEY UPDATE
+        pickled_olm_session = ${pickledOlmSession}
+    `,
+  );
+}
+
+export { createOlmSession };
diff --git a/keyserver/src/utils/olm-utils.js b/keyserver/src/utils/olm-utils.js
--- a/keyserver/src/utils/olm-utils.js
+++ b/keyserver/src/utils/olm-utils.js
@@ -4,9 +4,12 @@
 import type {
   Account as OlmAccount,
   Utility as OlmUtility,
+  Session as OlmSession,
 } from '@commapp/olm';
 import uuid from 'uuid';
 
+import { olmEncryptedMessageTypes } from 'lib/types/crypto-types.js';
+
 type PickledOlmAccount = {
   +picklingKey: string,
   +pickledAccount: string,
@@ -42,6 +45,29 @@
   return account;
 }
 
+async function createPickledOlmSession(
+  account: OlmAccount,
+  accountPicklingKey: string,
+  initialEncryptedMessage: string,
+): Promise<string> {
+  await olm.init();
+  const session = new olm.Session();
+  session.create_inbound(account, initialEncryptedMessage);
+  account.remove_one_time_keys(session);
+  session.decrypt(olmEncryptedMessageTypes.PREKEY, initialEncryptedMessage);
+  return session.pickle(accountPicklingKey);
+}
+
+async function unpickleOlmSession(
+  pickledSession: string,
+  picklingKey: string,
+): Promise<OlmSession> {
+  await olm.init();
+  const session = new olm.Session();
+  session.unpickle(picklingKey, pickledSession);
+  return session;
+}
+
 let cachedOLMUtility: OlmUtility;
 function getOlmUtility(): OlmUtility {
   if (cachedOLMUtility) {
@@ -75,7 +101,9 @@
 
 export {
   createPickledOlmAccount,
+  createPickledOlmSession,
   getOlmUtility,
   unpickleOlmAccount,
+  unpickleOlmSession,
   validateAccountPrekey,
 };
diff --git a/lib/types/crypto-types.js b/lib/types/crypto-types.js
--- a/lib/types/crypto-types.js
+++ b/lib/types/crypto-types.js
@@ -37,3 +37,8 @@
   +payload: string,
   +signature: string,
 };
+
+export const olmEncryptedMessageTypes = Object.freeze({
+  PREKEY: 0,
+  TEXT: 1,
+});