diff --git a/.vscode/launch.json b/.vscode/launch.json
index fa0592871a..5b91c48101 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -48,10 +48,12 @@
       "request": "launch",
       "args": ["src/__start.ts"],
       "env": {
-        "MONGO_URL": "mongodb://localhost:27018",
+        "MONGO_URL": "mongodb://localhost:27017",
         "SERVER_SECRET": "secret",
         "TRANSACTOR_URL": "ws:/localhost:3333",
-        "ACCOUNT_PORT": "3000"
+        "ACCOUNT_PORT": "3000",
+        "FRONT_URL": "http://localhost:8080",
+        "SES_URL": "http://localhost:8091"
       },
       "runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
       "sourceMaps": true,
diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml
index 07803b3bd2..b636abdb55 100644
--- a/common/config/rush/pnpm-lock.yaml
+++ b/common/config/rush/pnpm-lock.yaml
@@ -319,6 +319,7 @@ specifiers:
   mini-css-extract-plugin: ^2.2.0
   minio: ^7.0.26
   mongodb: ^4.11.0
+  node-fetch: ^2.6.6
   p-queue: ~7.3.0
   pdfkit: ~0.13.0
   postcss: ^8.4.20
@@ -686,6 +687,7 @@ dependencies:
   mini-css-extract-plugin: 2.6.1_webpack@5.75.0
   minio: 7.0.32
   mongodb: 4.11.0
+  node-fetch: 2.6.7
   p-queue: 7.3.0
   pdfkit: 0.13.0
   postcss: 8.4.20
@@ -3427,6 +3429,13 @@ packages:
       '@types/node': 16.11.68
     dev: false
 
+  /@types/node-fetch/2.6.2:
+    resolution: {integrity: sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==}
+    dependencies:
+      '@types/node': 16.11.68
+      form-data: 3.0.1
+    dev: false
+
   /@types/node/12.20.24:
     resolution: {integrity: sha512-yxDeaQIAJlMav7fH5AQqPH1u8YIuhYJXYBzxaQ4PifsU0GDO38MSdmEDeRlIxrKbC6NbEaaEHDanWb+y30U8SQ==}
     dev: false
@@ -11462,13 +11471,14 @@ packages:
     dev: false
 
   file:projects/account.tgz:
-    resolution: {integrity: sha512-1O91BX+tpcoZjaOTfyy1CvFF8Bea7RUvkQShNBsMgMthz1S4C82Q5htsCJDYSdm7TC3qDORBJGG4VBVN/Pwcrw==, tarball: file:projects/account.tgz}
+    resolution: {integrity: sha512-aJKpRhl1z/VBWuJpIUi71qe6EicrFQjmyQ7wGSDBM/b1M6Sm9wKSTBOjjhknIHNwno2toNAEmyV0nkwZcit9hQ==, tarball: file:projects/account.tgz}
     name: '@rush-temp/account'
     version: 0.0.0
     dependencies:
       '@rushstack/heft': 0.47.11
       '@types/heft-jest': 1.0.3
       '@types/minio': 7.0.14
+      '@types/node-fetch': 2.6.2
       '@types/ws': 8.5.3
       '@typescript-eslint/eslint-plugin': 5.42.1_d506b9be61cb4ac2646ecbc6e0680464
       '@typescript-eslint/parser': 5.42.1_eslint@8.27.0+typescript@4.8.4
@@ -11479,12 +11489,14 @@ packages:
       eslint-plugin-promise: 6.1.1_eslint@8.27.0
       minio: 7.0.32
       mongodb: 4.11.0
+      node-fetch: 2.6.7
       prettier: 2.7.1
       typescript: 4.8.4
       ws: 8.11.0
     transitivePeerDependencies:
       - aws-crt
       - bufferutil
+      - encoding
       - supports-color
       - utf-8-validate
     dev: false
@@ -13477,7 +13489,7 @@ packages:
     dev: false
 
   file:projects/model-recruit.tgz_typescript@4.8.4:
-    resolution: {integrity: sha512-d2S1FnV9nadIWl5JBjdxs7lZo109fWavsI6SVq/lc6enCzQWas5m/ZVu7dF9HvT9OaD+6HHnqx3pQ/oqTl3fSw==, tarball: file:projects/model-recruit.tgz}
+    resolution: {integrity: sha512-KqP/U4mkxWwAYH7pD0Z//Cv+/CXMxRrwdnlWRoJOr3nKGFshVb64dRAdkgVdRYNG2rcIlNeaFrBKuqW+6rdjBw==, tarball: file:projects/model-recruit.tgz}
     id: file:projects/model-recruit.tgz
     name: '@rush-temp/model-recruit'
     version: 0.0.0
diff --git a/dev/docker-compose.yaml b/dev/docker-compose.yaml
index b2376604e2..f84714c09a 100644
--- a/dev/docker-compose.yaml
+++ b/dev/docker-compose.yaml
@@ -58,6 +58,8 @@ services:
       - MINIO_ENDPOINT=minio
       - MINIO_ACCESS_KEY=minioadmin
       - MINIO_SECRET_KEY=minioadmin
+      - FRONT_URL=http://localhost:8087
+      - SES_URL=http://localhost:8091
     restart: unless-stopped
   collaborator:
     image: hardcoreeng/collaborator
diff --git a/plugins/login-assets/lang/en.json b/plugins/login-assets/lang/en.json
index ab8fa4d11b..3445e2d1a4 100644
--- a/plugins/login-assets/lang/en.json
+++ b/plugins/login-assets/lang/en.json
@@ -29,6 +29,11 @@
     "NotSeeingWorkspace": "Not seeing your workspace?",
     "WorkspaceNameRule": "The workspace name can contains lowercase letters, numbers, and symbols !@#%&^-",
     "LinkValidHours": "Link valid (hours):",
-    "GetLink": "Get invite link"
+    "GetLink": "Get invite link",
+    "ForgotPassword": "Forgot your password",
+    "KnowPassword": "Know your password?",
+    "Recover": "Recover",
+    "PasswordRecovery": "Password recovery",
+    "RecoveryLinkSent": "Password recovery link sent to email"
   }
 }
\ No newline at end of file
diff --git a/plugins/login-assets/lang/ru.json b/plugins/login-assets/lang/ru.json
index 7d5a5f0ed1..10097ab928 100644
--- a/plugins/login-assets/lang/ru.json
+++ b/plugins/login-assets/lang/ru.json
@@ -29,6 +29,11 @@
     "NotSeeingWorkspace": "Не видите ваше рабочее пространство?",
     "WorkspaceNameRule": "Название рабочего пространства должно состояить из строчных латинских букв, цифр и символов !@#%&^-",
     "LinkValidHours": "Ссылка действительна (часов):",
-    "GetLink": "Получить ссылку"
+    "GetLink": "Получить ссылку",
+    "ForgotPassword": "Забыли пароль?",
+    "KnowPassword": "Знаете пароль?",
+    "Recover": "Восстановить",
+    "PasswordRecovery": "Восстановление пароля",
+    "RecoveryLinkSent": "Ссылка для восстановления пароля отправлена на почту"
   }
 }
\ No newline at end of file
diff --git a/plugins/login-resources/src/components/CreateWorkspaceForm.svelte b/plugins/login-resources/src/components/CreateWorkspaceForm.svelte
index b7beefbd9c..ad4a327842 100644
--- a/plugins/login-resources/src/components/CreateWorkspaceForm.svelte
+++ b/plugins/login-resources/src/components/CreateWorkspaceForm.svelte
@@ -73,12 +73,16 @@
   {fields}
   {object}
   {action}
-  bottomCaption={login.string.HaveWorkspace}
-  bottomActionLabel={login.string.SelectWorkspace}
-  bottomActionFunc={() => {
-    const loc = getCurrentLocation()
-    loc.path[1] = 'selectWorkspace'
-    loc.path.length = 2
-    navigate(loc)
-  }}
+  bottomActions={[
+    {
+      caption: login.string.HaveWorkspace,
+      i18n: login.string.SelectWorkspace,
+      func: () => {
+        const loc = getCurrentLocation()
+        loc.path[1] = 'selectWorkspace'
+        loc.path.length = 2
+        navigate(loc)
+      }
+    }
+  ]}
 />
diff --git a/plugins/login-resources/src/components/Form.svelte b/plugins/login-resources/src/components/Form.svelte
index 37214309d6..0b0ab4ce67 100644
--- a/plugins/login-resources/src/components/Form.svelte
+++ b/plugins/login-resources/src/components/Form.svelte
@@ -38,15 +38,19 @@
     func: () => Promise<void>
   }
 
+  interface BottomAction {
+    i18n: IntlString
+    func: () => void
+    caption: IntlString
+  }
+
   export let caption: IntlString
   export let status: Status
   export let fields: Field[]
   export let action: Action
   export let secondaryButtonLabel: IntlString | undefined = undefined
   export let secondaryButtonAction: (() => void) | undefined = undefined
-  export let bottomCaption: IntlString | undefined = undefined
-  export let bottomActionLabel: IntlString | undefined = undefined
-  export let bottomActionFunc: (() => void) | undefined = undefined
+  export let bottomActions: BottomAction[] = []
   export let object: any
 
   async function validate () {
@@ -152,15 +156,15 @@
       {/if}
     </div>
   </Scroller>
-  {#if bottomCaption || (bottomActionLabel && bottomActionFunc)}
+  {#if bottomActions.length}
     <div class="grow-separator" />
     <div class="footer">
-      {#if bottomCaption}
-        <span><Label label={bottomCaption} /></span>
-      {/if}
-      {#if bottomActionLabel && bottomActionFunc}
-        <a href="." on:click|preventDefault={bottomActionFunc}><Label label={bottomActionLabel} /></a>
-      {/if}
+      {#each bottomActions as action}
+        <div>
+          <span><Label label={action.caption} /></span>
+          <a href="." on:click|preventDefault={action.func}><Label label={action.i18n} /></a>
+        </div>
+      {/each}
     </div>
   {/if}
 </form>
diff --git a/plugins/login-resources/src/components/Join.svelte b/plugins/login-resources/src/components/Join.svelte
index 174ae1345f..614554d6db 100644
--- a/plugins/login-resources/src/components/Join.svelte
+++ b/plugins/login-resources/src/components/Join.svelte
@@ -84,13 +84,24 @@
     }
   }
 
-  $: bottomCaption = page === 'login' ? login.string.DoNotHaveAnAccount : login.string.HaveAccount
-  $: bottomActionLabel = page === 'login' ? login.string.SignUp : login.string.LogIn
+  $: bottom = page === 'login' ? [signUpAction] : [loginAction]
   $: secondaryButtonLabel = page === 'login' ? login.string.SignUp : undefined
   $: secondaryButtonAction = () => {
     page = 'signUp'
   }
 
+  const signUpAction = {
+    caption: login.string.DoNotHaveAnAccount,
+    i18n: login.string.SignUp,
+    func: () => (page = 'signUp')
+  }
+
+  const loginAction = {
+    caption: login.string.HaveAccount,
+    i18n: login.string.LogIn,
+    func: () => (page = 'login')
+  }
+
   onMount(() => {
     check()
   })
@@ -121,9 +132,5 @@
   {action}
   {secondaryButtonLabel}
   {secondaryButtonAction}
-  {bottomCaption}
-  {bottomActionLabel}
-  bottomActionFunc={() => {
-    page = page === 'login' ? 'signUp' : 'login'
-  }}
+  bottomActions={bottom}
 />
diff --git a/plugins/login-resources/src/components/LoginApp.svelte b/plugins/login-resources/src/components/LoginApp.svelte
index c423cc370f..070bf492b3 100644
--- a/plugins/login-resources/src/components/LoginApp.svelte
+++ b/plugins/login-resources/src/components/LoginApp.svelte
@@ -25,6 +25,8 @@
   import { onDestroy } from 'svelte'
   import login from '../plugin'
   import { getMetadata } from '@hcengineering/platform'
+  import PasswordRequest from './PasswordRequest.svelte'
+  import PasswordRestore from './PasswordRestore.svelte'
 
   export let page: string = 'login'
 
@@ -58,6 +60,10 @@
       <SignupForm />
     {:else if page === 'createWorkspace'}
       <CreateWorkspaceForm />
+    {:else if page === 'password'}
+      <PasswordRequest />
+    {:else if page === 'recovery'}
+      <PasswordRestore />
     {:else if page === 'selectWorkspace'}
       <SelectWorkspace {navigateUrl} />
     {:else if page === 'join'}
diff --git a/plugins/login-resources/src/components/LoginForm.svelte b/plugins/login-resources/src/components/LoginForm.svelte
index ecd4186856..b327aee8f0 100644
--- a/plugins/login-resources/src/components/LoginForm.svelte
+++ b/plugins/login-resources/src/components/LoginForm.svelte
@@ -14,11 +14,11 @@
 // limitations under the License.
 -->
 <script lang="ts">
-  import { OK, Status, Severity, setMetadata } from '@hcengineering/platform'
+  import { OK, setMetadata, Severity, Status } from '@hcengineering/platform'
   import { getCurrentLocation, navigate, setMetadataLocalStorage } from '@hcengineering/ui'
 
-  import Form from './Form.svelte'
   import { doLogin } from '../utils'
+  import Form from './Form.svelte'
 
   import login from '../plugin'
 
@@ -63,20 +63,28 @@
       }
     }
   }
+
+  const recoveryAction = {
+    caption: login.string.ForgotPassword,
+    i18n: login.string.Recover,
+    func: () => {
+      const loc = getCurrentLocation()
+      loc.path[1] = 'password'
+      loc.path.length = 2
+      navigate(loc)
+    }
+  }
+
+  const signUpAction = {
+    caption: login.string.DoNotHaveAnAccount,
+    i18n: login.string.SignUp,
+    func: () => {
+      const loc = getCurrentLocation()
+      loc.path[1] = 'signup'
+      loc.path.length = 2
+      navigate(loc)
+    }
+  }
 </script>
 
-<Form
-  caption={login.string.LogIn}
-  {status}
-  {fields}
-  {object}
-  {action}
-  bottomCaption={login.string.DoNotHaveAnAccount}
-  bottomActionLabel={login.string.SignUp}
-  bottomActionFunc={() => {
-    const loc = getCurrentLocation()
-    loc.path[1] = 'signup'
-    loc.path.length = 2
-    navigate(loc)
-  }}
-/>
+<Form caption={login.string.LogIn} {status} {fields} {object} {action} bottomActions={[recoveryAction, signUpAction]} />
diff --git a/plugins/login-resources/src/components/PasswordRequest.svelte b/plugins/login-resources/src/components/PasswordRequest.svelte
new file mode 100644
index 0000000000..4e69042a60
--- /dev/null
+++ b/plugins/login-resources/src/components/PasswordRequest.svelte
@@ -0,0 +1,84 @@
+<!--
+// Copyright © 2023 Hardcore Engineering Inc.
+//
+// Licensed under the Eclipse Public License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License. You may
+// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//
+// See the License for the specific language governing permissions and
+// limitations under the License.
+-->
+<script lang="ts">
+  import { OK, Severity, Status } from '@hcengineering/platform'
+  import { MessageBox } from '@hcengineering/presentation'
+
+  import { getCurrentLocation, navigate, showPopup } from '@hcengineering/ui'
+  import login from '../plugin'
+  import { requestPassword } from '../utils'
+  import Form from './Form.svelte'
+
+  const fields = [{ id: 'email', name: 'username', i18n: login.string.Email }]
+
+  const object = {
+    username: ''
+  }
+
+  let status: Status<any> = OK
+
+  const action = {
+    i18n: login.string.Recover,
+    func: async () => {
+      status = new Status(Severity.INFO, login.status.ConnectingToServer, {})
+
+      const loginStatus = await requestPassword(object.username)
+      status = loginStatus
+      if (loginStatus === OK) {
+        showPopup(
+          MessageBox,
+          {
+            label: login.string.PasswordRecovery,
+            message: login.string.RecoveryLinkSent,
+            canSubmit: false
+          },
+          undefined,
+          () => {
+            const loc = getCurrentLocation()
+            loc.path[1] = 'login'
+            navigate(loc)
+          }
+        )
+      }
+    }
+  }
+
+  const signUpAction = {
+    caption: login.string.DoNotHaveAnAccount,
+    i18n: login.string.SignUp,
+    func: () => {
+      const loc = getCurrentLocation()
+      loc.path[1] = 'signup'
+      loc.path.length = 2
+      navigate(loc)
+    }
+  }
+
+  const bottomActions = [
+    {
+      caption: login.string.KnowPassword,
+      i18n: login.string.LogIn,
+      func: () => {
+        const loc = getCurrentLocation()
+        loc.path[1] = 'login'
+        loc.path.length = 2
+        navigate(loc)
+      }
+    },
+    signUpAction
+  ]
+</script>
+
+<Form caption={login.string.PasswordRecovery} {status} {fields} {object} {action} {bottomActions} />
diff --git a/plugins/login-resources/src/components/PasswordRestore.svelte b/plugins/login-resources/src/components/PasswordRestore.svelte
new file mode 100644
index 0000000000..096eae19b9
--- /dev/null
+++ b/plugins/login-resources/src/components/PasswordRestore.svelte
@@ -0,0 +1,59 @@
+<!--
+// Copyright © 2023 Hardcore Engineering Inc.
+//
+// Licensed under the Eclipse Public License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License. You may
+// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//
+// See the License for the specific language governing permissions and
+// limitations under the License.
+-->
+<script lang="ts">
+  import { OK, setMetadata, Severity, Status } from '@hcengineering/platform'
+
+  import { getCurrentLocation, navigate, setMetadataLocalStorage } from '@hcengineering/ui'
+  import login from '../plugin'
+  import { restorePassword } from '../utils'
+  import Form from './Form.svelte'
+
+  const fields = [
+    { id: 'new-password', name: 'password', i18n: login.string.Password, password: true },
+    { id: 'new-password', name: 'password2', i18n: login.string.PasswordRepeat, password: true }
+  ]
+
+  const object = {
+    password: '',
+    password2: ''
+  }
+
+  let status: Status<any> = OK
+
+  const action = {
+    i18n: login.string.Recover,
+    func: async () => {
+      const location = getCurrentLocation()
+      if (location.query?.id === undefined || location.query?.id === null) return
+      status = new Status(Severity.INFO, login.status.ConnectingToServer, {})
+
+      const [loginStatus, result] = await restorePassword(location.query?.id, object.password)
+
+      status = loginStatus
+
+      if (result !== undefined) {
+        setMetadata(login.metadata.LoginToken, result.token)
+        setMetadataLocalStorage(login.metadata.LoginEndpoint, result.endpoint)
+        setMetadataLocalStorage(login.metadata.LoginEmail, result.email)
+        const loc = getCurrentLocation()
+        loc.path[1] = 'selectWorkspace'
+        loc.path.length = 2
+        navigate(loc)
+      }
+    }
+  }
+</script>
+
+<Form caption={login.string.PasswordRecovery} {status} {fields} {object} {action} />
diff --git a/plugins/login-resources/src/components/SignupForm.svelte b/plugins/login-resources/src/components/SignupForm.svelte
index b50c253221..8db670c764 100644
--- a/plugins/login-resources/src/components/SignupForm.svelte
+++ b/plugins/login-resources/src/components/SignupForm.svelte
@@ -67,12 +67,16 @@
   {fields}
   {object}
   {action}
-  bottomCaption={login.string.HaveAccount}
-  bottomActionLabel={login.string.LogIn}
-  bottomActionFunc={() => {
-    const loc = getCurrentLocation()
-    loc.path[1] = 'login'
-    loc.path.length = 2
-    navigate(loc)
-  }}
+  bottomActions={[
+    {
+      caption: login.string.HaveAccount,
+      i18n: login.string.LogIn,
+      func: () => {
+        const loc = getCurrentLocation()
+        loc.path[1] = 'login'
+        loc.path.length = 2
+        navigate(loc)
+      }
+    }
+  ]}
 />
diff --git a/plugins/login-resources/src/plugin.ts b/plugins/login-resources/src/plugin.ts
index a0cfd7e409..b0777d39c7 100644
--- a/plugins/login-resources/src/plugin.ts
+++ b/plugins/login-resources/src/plugin.ts
@@ -50,6 +50,11 @@ export default mergeIds(loginId, login, {
     ChangeAccount: '' as IntlString,
     WorkspaceNameRule: '' as IntlString,
     LinkValidHours: '' as IntlString,
-    GetLink: '' as IntlString
+    GetLink: '' as IntlString,
+    ForgotPassword: '' as IntlString,
+    Recover: '' as IntlString,
+    KnowPassword: '' as IntlString,
+    PasswordRecovery: '' as IntlString,
+    RecoveryLinkSent: '' as IntlString
   }
 })
diff --git a/plugins/login-resources/src/utils.ts b/plugins/login-resources/src/utils.ts
index a4876bda95..fbe45c54e3 100644
--- a/plugins/login-resources/src/utils.ts
+++ b/plugins/login-resources/src/utils.ts
@@ -566,3 +566,65 @@ export async function leaveWorkspace (email: string): Promise<void> {
     body: serialize(request)
   })
 }
+
+export async function requestPassword (email: string): Promise<Status> {
+  const accountsUrl = getMetadata(login.metadata.AccountsUrl)
+
+  if (accountsUrl === undefined) {
+    throw new Error('accounts url not specified')
+  }
+
+  const overrideToken = getMetadata(login.metadata.OverrideLoginToken)
+  if (overrideToken !== undefined) {
+    const endpoint = getMetadata(login.metadata.OverrideEndpoint)
+    if (endpoint !== undefined) {
+      return OK
+    }
+  }
+  const request: Request<[string]> = {
+    method: 'requestPassword',
+    params: [email]
+  }
+
+  try {
+    const response = await fetch(accountsUrl, {
+      method: 'POST',
+      headers: {
+        'Content-Type': 'application/json'
+      },
+      body: serialize(request)
+    })
+    const result: Response<any> = await response.json()
+    return result.error ?? OK
+  } catch (err) {
+    return unknownError(err)
+  }
+}
+
+export async function restorePassword (token: string, password: string): Promise<[Status, LoginInfo | undefined]> {
+  const accountsUrl = getMetadata(login.metadata.AccountsUrl)
+
+  if (accountsUrl === undefined) {
+    throw new Error('accounts url not specified')
+  }
+
+  const request: Request<[string]> = {
+    method: 'restorePassword',
+    params: [password]
+  }
+
+  try {
+    const response = await fetch(accountsUrl, {
+      method: 'POST',
+      headers: {
+        Authorization: 'Bearer ' + token,
+        'Content-Type': 'application/json'
+      },
+      body: serialize(request)
+    })
+    const result: Response<any> = await response.json()
+    return [result.error ?? OK, result.result]
+  } catch (err) {
+    return [unknownError(err), undefined]
+  }
+}
diff --git a/pods/account/package.json b/pods/account/package.json
index 00bdb4919b..e98b81cae8 100644
--- a/pods/account/package.json
+++ b/pods/account/package.json
@@ -13,7 +13,7 @@
     "docker:build": "docker build -t hardcoreeng/account .",
     "docker:staging": "../../common/scripts/docker_tag.sh hardcoreeng/account staging",
     "docker:push": "../../common/scripts/docker_tag.sh hardcoreeng/account",
-    "run-local": "cross-env MONGO_URL=mongodb://localhost:27017 MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin MINIO_ENDPOINT=localhost SERVER_SECRET='secret' TRANSACTOR_URL=ws:/localhost:3333 ts-node src/__start.ts",
+    "run-local": "cross-env MONGO_URL=mongodb://localhost:27017 MINIO_ACCESS_KEY=minioadmi MINIO_SECRET_KEY=minioadmin MINIO_ENDPOINT=localhost SERVER_SECRET='secret' TRANSACTOR_URL=ws:/localhost:3333 ts-node src/__start.ts",
     "lint": "eslint src",
     "format": "prettier --write src && eslint --fix src"
   },
diff --git a/pods/account/src/index.ts b/pods/account/src/index.ts
index c9310ddd22..a8da0f7151 100644
--- a/pods/account/src/index.ts
+++ b/pods/account/src/index.ts
@@ -14,7 +14,7 @@
 // limitations under the License.
 //
 
-import { AccountMethod, ACCOUNT_DB } from '@hcengineering/account'
+import account, { AccountMethod, ACCOUNT_DB } from '@hcengineering/account'
 import platform, { Response, serialize, setMetadata, Severity, Status } from '@hcengineering/platform'
 import serverToken from '@hcengineering/server-token'
 import toolPlugin from '@hcengineering/server-tool'
@@ -50,6 +50,12 @@ export function serveAccount (methods: Record<string, AccountMethod>, productId
     process.exit(1)
   }
 
+  const ses = process.env.SES_URL
+  const frontURL = process.env.FRONT_URL
+
+  setMetadata(account.metadata.SES_URL, ses)
+  setMetadata(account.metadata.FrontURL, frontURL)
+
   setMetadata(serverToken.metadata.Secret, serverSecret)
   setMetadata(toolPlugin.metadata.Endpoint, endpointUri)
   setMetadata(toolPlugin.metadata.Transactor, transactorUri)
diff --git a/server/account/package.json b/server/account/package.json
index ded420da24..a5dda22781 100644
--- a/server/account/package.json
+++ b/server/account/package.json
@@ -24,7 +24,8 @@
     "prettier": "^2.7.1",
     "@rushstack/heft": "^0.47.9",
     "typescript": "^4.3.5",
-    "@types/ws": "^8.5.3"
+    "@types/ws": "^8.5.3",
+    "@types/node-fetch": "~2.6.2"
   },
   "dependencies": {
     "mongodb": "^4.11.0",
@@ -37,6 +38,7 @@
     "@hcengineering/model": "^0.6.0",
     "@hcengineering/server-tool": "^0.6.0",
     "@hcengineering/server-token": "^0.6.0",
-    "@hcengineering/model-all": "^0.6.0"
+    "@hcengineering/model-all": "^0.6.0",
+    "node-fetch": "^2.6.6"
   }
 }
diff --git a/server/account/src/index.ts b/server/account/src/index.ts
index 468ae8980a..ea4e17a48a 100644
--- a/server/account/src/index.ts
+++ b/server/account/src/index.ts
@@ -34,6 +34,7 @@ import core, {
 import { MigrateOperation } from '@hcengineering/model'
 import platform, {
   getMetadata,
+  Metadata,
   PlatformError,
   Plugin,
   plugin,
@@ -47,6 +48,7 @@ import { decodeToken, generateToken } from '@hcengineering/server-token'
 import toolPlugin, { connect, initModel, upgradeModel } from '@hcengineering/server-tool'
 import { pbkdf2Sync, randomBytes } from 'crypto'
 import { Binary, Db, Filter, ObjectId } from 'mongodb'
+import fetch from 'node-fetch'
 
 const WORKSPACE_COLLECTION = 'workspace'
 const ACCOUNT_COLLECTION = 'account'
@@ -73,6 +75,10 @@ const accountPlugin = plugin(accountId, {
     AccountAlreadyExists: '' as StatusCode<{ account: string }>,
     WorkspaceAlreadyExists: '' as StatusCode<{ workspace: string }>,
     ProductIdMismatch: '' as StatusCode<{ productId: string }>
+  },
+  metadata: {
+    FrontURL: '' as Metadata<string>,
+    SES_URL: '' as Metadata<string>
   }
 })
 
@@ -637,7 +643,7 @@ export async function replacePassword (db: Db, productId: string, email: string,
   const account = await getAccount(db, email)
 
   if (account === null) {
-    throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.InvalidPassword, { account: email }))
+    throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.AccountNotFound, { account: email }))
   }
   const salt = randomBytes(32)
   const hash = hashWithSalt(password, salt)
@@ -645,6 +651,73 @@ export async function replacePassword (db: Db, productId: string, email: string,
   await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { salt, hash } })
 }
 
+/**
+ * @public
+ */
+export async function requestPassword (db: Db, productId: string, email: string): Promise<void> {
+  const account = await getAccount(db, email)
+
+  if (account === null) {
+    throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.AccountNotFound, { account: email }))
+  }
+
+  const sesURL = getMetadata(accountPlugin.metadata.SES_URL)
+  if (sesURL === undefined || sesURL === '') {
+    throw new Error('Please provide email service url')
+  }
+  const front = getMetadata(accountPlugin.metadata.FrontURL)
+  if (front === undefined || front === '') {
+    throw new Error('Please provide front url')
+  }
+
+  const token = generateToken('@restore', getWorkspaceId('', productId), {
+    restore: email
+  })
+
+  const link = `${front}/login/recovery?id=${token}`
+
+  const text = `We received a request to reset the password for your account. To reset your password, please paste the following link in your web browser's address bar: ${link}. If you have not ordered a password recovery just ignore this letter.`
+  const html = `<p>We received a request to reset the password for your account. To reset your password, please click the link below: <a href=${link}>Reset password</a></p><p>
+  If the Reset password link above does not work, paste the following link in your web browser's address bar: ${link}
+</p><p>If you have not ordered a password recovery just ignore this letter.</p>`
+  const subject = 'Password recovery'
+  const to = account.email
+  await fetch(`${sesURL}/send`, {
+    method: 'post',
+    headers: {
+      'Content-Type': 'application/json'
+    },
+    body: JSON.stringify({
+      text,
+      html,
+      subject,
+      to
+    })
+  })
+}
+
+/**
+ * @public
+ */
+export async function restorePassword (db: Db, productId: string, token: string, password: string): Promise<LoginInfo> {
+  const decode = decodeToken(token)
+  const email = decode.extra?.restore
+  if (email === undefined) {
+    throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.AccountNotFound, { account: accountId }))
+  }
+  const account = await getAccount(db, email)
+
+  if (account === null) {
+    throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.AccountNotFound, { account: accountId }))
+  }
+  const salt = randomBytes(32)
+  const hash = hashWithSalt(password, salt)
+
+  await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { salt, hash } })
+
+  return await login(db, productId, email, password)
+}
+
 /**
  * @public
  */
@@ -868,7 +941,9 @@ export function getMethods (
     leaveWorkspace: wrap(leaveWorkspace),
     listWorkspaces: wrap(listWorkspaces),
     changeName: wrap(changeName),
-    changePassword: wrap(changePassword)
+    changePassword: wrap(changePassword),
+    requestPassword: wrap(requestPassword),
+    restorePassword: wrap(restorePassword)
     // updateAccount: wrap(updateAccount)
   }
 }