diff --git a/ee/app/assets/javascripts/escalation_policies/components/add_edit_escalation_policy_form.vue b/ee/app/assets/javascripts/escalation_policies/components/add_edit_escalation_policy_form.vue
index d058554fbd2f3ffaa6d25ddf7bdc81cb3adf9d5b..5c8b1b677b368fee0086a1f1d7c9a6b0a17c183f 100644
--- a/ee/app/assets/javascripts/escalation_policies/components/add_edit_escalation_policy_form.vue
+++ b/ee/app/assets/javascripts/escalation_policies/components/add_edit_escalation_policy_form.vue
@@ -3,7 +3,12 @@ import { GlLink, GlForm, GlFormGroup, GlFormInput } from '@gitlab/ui';
 import { cloneDeep, uniqueId } from 'lodash';
 import createFlash from '~/flash';
 import { s__, __ } from '~/locale';
-import { DEFAULT_ACTION, DEFAULT_ESCALATION_RULE, MAX_RULES_LENGTH } from '../constants';
+import {
+  EMAIL_ONCALL_SCHEDULE_USER,
+  DEFAULT_ESCALATION_RULE,
+  EMAIL_USER,
+  MAX_RULES_LENGTH,
+} from '../constants';
 import getOncallSchedulesQuery from '../graphql/queries/get_oncall_schedules.query.graphql';
 import EscalationRule from './escalation_rule.vue';
 
@@ -78,17 +83,14 @@ export default {
   },
   mounted() {
     this.rules = this.form.rules.map((rule) => {
-      const {
-        status,
-        elapsedTimeMinutes,
-        oncallSchedule: { iid: oncallScheduleIid },
-      } = rule;
+      const { status, elapsedTimeMinutes, oncallSchedule, user } = rule;
 
       return {
         status,
         elapsedTimeMinutes,
-        action: DEFAULT_ACTION,
-        oncallScheduleIid,
+        action: user ? EMAIL_USER : EMAIL_ONCALL_SCHEDULE_USER,
+        oncallScheduleIid: oncallSchedule?.iid,
+        username: user?.username,
         key: uniqueId(),
       };
     });
@@ -102,7 +104,8 @@ export default {
       this.rules.push({ ...cloneDeep(DEFAULT_ESCALATION_RULE), key: uniqueId() });
     },
     updateEscalationRules({ rule, index }) {
-      this.rules[index] = { ...this.rules[index], ...rule };
+      const { key } = this.rules[index];
+      this.rules[index] = { key, ...rule };
       this.emitRulesUpdate();
     },
     removeEscalationRule(index) {
diff --git a/ee/app/assets/javascripts/escalation_policies/components/add_edit_escalation_policy_modal.vue b/ee/app/assets/javascripts/escalation_policies/components/add_edit_escalation_policy_modal.vue
index 2baa9fbf511c8cdb4261390b6f39380f36fb3db0..340736a3d78fa0f84d53b4e34be44794ebaba382 100644
--- a/ee/app/assets/javascripts/escalation_policies/components/add_edit_escalation_policy_modal.vue
+++ b/ee/app/assets/javascripts/escalation_policies/components/add_edit_escalation_policy_modal.vue
@@ -9,7 +9,7 @@ import {
 import createEscalationPolicyMutation from '../graphql/mutations/create_escalation_policy.mutation.graphql';
 import updateEscalationPolicyMutation from '../graphql/mutations/update_escalation_policy.mutation.graphql';
 import getEscalationPoliciesQuery from '../graphql/queries/get_escalation_policies.query.graphql';
-import { isNameFieldValid, getRulesValidationState, serializeRule } from '../utils';
+import { isNameFieldValid, getRulesValidationState, serializeRule, getRules } from '../utils';
 import AddEditEscalationPolicyForm from './add_edit_escalation_policy_form.vue';
 
 export const i18n = {
@@ -82,7 +82,8 @@ export default {
         this.validationState.name &&
         (this.isEditMode ? true : this.validationState.rules.length) &&
         this.validationState.rules.every(
-          ({ isTimeValid, isScheduleValid }) => isTimeValid && isScheduleValid,
+          ({ isTimeValid, isScheduleValid, isUserValid }) =>
+            isTimeValid && isScheduleValid && isUserValid,
         )
       );
     },
@@ -90,12 +91,12 @@ export default {
       return (
         this.form.name !== this.initialState.name ||
         this.form.description !== this.initialState.description ||
-        !isEqual(this.getRules(this.form.rules), this.getRules(this.initialState.rules))
+        !isEqual(getRules(this.form.rules), getRules(this.initialState.rules))
       );
     },
     requestParams() {
       const id = this.isEditMode ? { id: this.escalationPolicy.id } : {};
-      return { ...this.form, ...id, rules: this.getRules(this.form.rules).map(serializeRule) };
+      return { ...this.form, ...id, rules: getRules(this.form.rules).map(serializeRule) };
     },
   },
   methods: {
@@ -188,15 +189,6 @@ export default {
           this.loading = false;
         });
     },
-    getRules(rules) {
-      return rules.map(
-        ({ status, elapsedTimeMinutes, oncallScheduleIid, oncallSchedule: { iid } = {} }) => ({
-          status,
-          elapsedTimeMinutes,
-          oncallScheduleIid: oncallScheduleIid || iid,
-        }),
-      );
-    },
     validateForm(field) {
       if (field === 'name') {
         this.validationState.name = isNameFieldValid(this.form.name);
diff --git a/ee/app/assets/javascripts/escalation_policies/components/escalation_policy.vue b/ee/app/assets/javascripts/escalation_policies/components/escalation_policy.vue
index 68b3e98a66fd6dab11d1e22d295590627da66147..f269937df8a1dbb8bdfb92912ae7444b016b8829 100644
--- a/ee/app/assets/javascripts/escalation_policies/components/escalation_policy.vue
+++ b/ee/app/assets/javascripts/escalation_policies/components/escalation_policy.vue
@@ -8,14 +8,17 @@ import {
   GlSprintf,
   GlIcon,
   GlCollapse,
+  GlToken,
+  GlAvatar,
 } from '@gitlab/ui';
 import { s__, __ } from '~/locale';
 import {
   ACTIONS,
   ALERT_STATUSES,
-  DEFAULT_ACTION,
+  EMAIL_ONCALL_SCHEDULE_USER,
   deleteEscalationPolicyModalId,
   editEscalationPolicyModalId,
+  EMAIL_USER,
 } from '../constants';
 import EditEscalationPolicyModal from './add_edit_escalation_policy_modal.vue';
 import DeleteEscalationPolicyModal from './delete_escalation_policy_modal.vue';
@@ -24,22 +27,22 @@ export const i18n = {
   editPolicy: s__('EscalationPolicies|Edit escalation policy'),
   deletePolicy: s__('EscalationPolicies|Delete escalation policy'),
   escalationRule: s__(
-    'EscalationPolicies|IF alert is not %{alertStatus} in %{minutes} %{then} THEN %{doAction} %{schedule}',
+    'EscalationPolicies|IF alert is not %{alertStatus} in %{minutes} %{then} THEN %{doAction} %{scheduleOrUser}',
   ),
   minutes: s__('EscalationPolicies|mins'),
   noRules: s__('EscalationPolicies|This policy has no escalation rules.'),
 };
 
-const isRuleValid = ({ status, elapsedTimeMinutes, oncallSchedule: { name } }) =>
+const isRuleValid = ({ status, elapsedTimeMinutes, oncallSchedule, user }) =>
   Object.keys(ALERT_STATUSES).includes(status) &&
   typeof elapsedTimeMinutes === 'number' &&
-  typeof name === 'string';
+  (typeof oncallSchedule?.name === 'string' || typeof user?.username === 'string');
 
 export default {
   i18n,
   ACTIONS,
   ALERT_STATUSES,
-  DEFAULT_ACTION,
+  EMAIL_ONCALL_SCHEDULE_USER,
   components: {
     GlButton,
     GlButtonGroup,
@@ -47,6 +50,8 @@ export default {
     GlSprintf,
     GlIcon,
     GlCollapse,
+    GlToken,
+    GlAvatar,
     DeleteEscalationPolicyModal,
     EditEscalationPolicyModal,
   },
@@ -87,6 +92,20 @@ export default {
       return `${deleteEscalationPolicyModalId}-${this.policy.id}`;
     },
   },
+  methods: {
+    hasEscalationSchedule(rule) {
+      return rule.oncallSchedule?.iid;
+    },
+    hasEscalationUser(rule) {
+      return rule.user?.username;
+    },
+    getActionName(rule) {
+      return (this.hasEscalationSchedule(rule)
+        ? ACTIONS[EMAIL_ONCALL_SCHEDULE_USER]
+        : ACTIONS[EMAIL_USER]
+      ).toLowerCase();
+    },
+  },
 };
 </script>
 
@@ -147,6 +166,7 @@ export default {
               v-for="(rule, ruleIndex) in policy.rules"
               :key="rule.id"
               :class="{ 'gl-mb-5': ruleIndex !== policy.rules.length - 1 }"
+              class="gl-display-flex gl-align-items-center"
             >
               <gl-icon name="clock" class="gl-mr-3" />
               <gl-sprintf :message="$options.i18n.escalationRule">
@@ -155,7 +175,7 @@ export default {
                 </template>
                 <template #minutes>
                   <span class="gl-font-weight-bold">
-                    {{ rule.elapsedTimeMinutes }} {{ $options.i18n.minutes }}
+                    &nbsp;{{ rule.elapsedTimeMinutes }} {{ $options.i18n.minutes }}
                   </span>
                 </template>
                 <template #then>
@@ -165,12 +185,17 @@ export default {
                   <gl-icon name="notifications" class="gl-mr-3" />
                 </template>
                 <template #doAction>
-                  {{ $options.ACTIONS[$options.DEFAULT_ACTION].toLowerCase() }}
+                  {{ getActionName(rule) }}
+                  &nbsp;
                 </template>
-                <template #schedule>
-                  <span class="gl-font-weight-bold">
+                <template #scheduleOrUser>
+                  <span v-if="hasEscalationSchedule(rule)" class="gl-font-weight-bold">
                     {{ rule.oncallSchedule.name }}
                   </span>
+                  <gl-token v-else-if="hasEscalationUser(rule)" view-only>
+                    <gl-avatar :src="rule.user.avatarUrl" :size="16" />
+                    {{ rule.user.name }}
+                  </gl-token>
                 </template>
               </gl-sprintf>
             </div>
diff --git a/ee/app/assets/javascripts/escalation_policies/components/escalation_rule.vue b/ee/app/assets/javascripts/escalation_policies/components/escalation_rule.vue
index 75e30f734390957bfe11b2eafe17a1d5ba3a78bf..c1bffbabde56bbc31a8743e09d1a3513ae6b7b52 100644
--- a/ee/app/assets/javascripts/escalation_policies/components/escalation_rule.vue
+++ b/ee/app/assets/javascripts/escalation_policies/components/escalation_rule.vue
@@ -11,13 +11,14 @@ import {
   GlTooltipDirective as GlTooltip,
 } from '@gitlab/ui';
 import { s__ } from '~/locale';
-import { ACTIONS, ALERT_STATUSES } from '../constants';
+import { ACTIONS, ALERT_STATUSES, EMAIL_ONCALL_SCHEDULE_USER, EMAIL_USER } from '../constants';
+import UserSelect from './user_select.vue';
 
 export const i18n = {
   fields: {
     rules: {
       condition: s__('EscalationPolicies|IF alert is not %{alertStatus} in %{minutes} minutes'),
-      action: s__('EscalationPolicies|THEN %{doAction} %{schedule}'),
+      action: s__('EscalationPolicies|THEN %{doAction} %{scheduleOrUser}'),
       selectSchedule: s__('EscalationPolicies|Select schedule'),
       noSchedules: s__(
         'EscalationPolicies|A schedule is required for adding an escalation policy. Please create an on-call schedule first.',
@@ -27,6 +28,9 @@ export const i18n = {
         'EscalationPolicies|A schedule is required for adding an escalation policy.',
       ),
       invalidTimeValidationMsg: s__('EscalationPolicies|Minutes must be between 0 and 1440.'),
+      invalidUserValidationMsg: s__(
+        'EscalationPolicies|A user is required for adding an escalation policy.',
+      ),
     },
   },
 };
@@ -35,6 +39,8 @@ export default {
   i18n,
   ALERT_STATUSES,
   ACTIONS,
+  EMAIL_ONCALL_SCHEDULE_USER,
+  EMAIL_USER,
   components: {
     GlFormGroup,
     GlFormInput,
@@ -44,6 +50,7 @@ export default {
     GlButton,
     GlIcon,
     GlSprintf,
+    UserSelect,
   },
   directives: {
     GlTooltip,
@@ -74,12 +81,15 @@ export default {
     },
   },
   data() {
-    const { status, elapsedTimeMinutes, action, oncallScheduleIid } = this.rule;
+    const { status, elapsedTimeMinutes, oncallScheduleIid, username, action } = this.rule;
+
     return {
       status,
-      elapsedTimeMinutes,
       action,
+      elapsedTimeMinutes,
       oncallScheduleIid,
+      username,
+      hasFocus: true,
     };
   },
   computed: {
@@ -92,7 +102,7 @@ export default {
       return !this.schedulesLoading && !this.schedules.length;
     },
     isValid() {
-      return this.isTimeValid && this.isScheduleValid;
+      return this.isTimeValid && this.isScheduleValid && this.isUserValid;
     },
     isTimeValid() {
       return this.validationState?.isTimeValid;
@@ -100,21 +110,71 @@ export default {
     isScheduleValid() {
       return this.validationState?.isScheduleValid;
     },
+    isUserValid() {
+      return this.validationState?.isUserValid;
+    },
+    isEmailOncallScheduleUserActionSelected() {
+      return this.action === EMAIL_ONCALL_SCHEDULE_USER;
+    },
+    isEmailUserActionSelected() {
+      return this.action === EMAIL_USER;
+    },
+    actionBasedRequestParams() {
+      if (this.isEmailOncallScheduleUserActionSelected) {
+        return { oncallScheduleIid: parseInt(this.oncallScheduleIid, 10) };
+      }
+
+      return { username: this.username };
+    },
+    showEmptyScheduleValidationMsg() {
+      return this.isEmailOncallScheduleUserActionSelected && !this.isScheduleValid;
+    },
+    showNoUserValidationMsg() {
+      return this.isEmailUserActionSelected && !this.isUserValid;
+    },
+  },
+  mounted() {
+    this.ruleContainer = this.$refs.ruleContainer?.$el;
+    this.ruleContainer?.addEventListener('focusin', this.addFocus);
+    this.ruleContainer?.addEventListener('focusout', this.removeFocus);
+  },
+  beforeDestroy() {
+    this.ruleContainer?.removeEventListener('focusin', this.addFocus);
+    this.ruleContainer?.removeEventListener('focusout', this.removeFocus);
   },
   methods: {
+    addFocus() {
+      this.hasFocus = true;
+    },
+    removeFocus() {
+      this.hasFocus = false;
+    },
     setOncallSchedule({ iid }) {
       this.oncallScheduleIid = this.oncallScheduleIid === iid ? null : iid;
       this.emitUpdate();
     },
+    setAction(action) {
+      this.action = action;
+      if (this.isEmailOncallScheduleUserActionSelected) {
+        this.username = null;
+      } else if (this.isEmailUserActionSelected) {
+        this.oncallScheduleIid = null;
+      }
+      this.emitUpdate();
+    },
     setStatus(status) {
       this.status = status;
       this.emitUpdate();
     },
+    setSelectedUser(username) {
+      this.username = username;
+      this.emitUpdate();
+    },
     emitUpdate() {
       this.$emit('update-escalation-rule', {
         index: this.index,
         rule: {
-          oncallScheduleIid: parseInt(this.oncallScheduleIid, 10),
+          ...this.actionBasedRequestParams,
           action: this.action,
           status: this.status,
           elapsedTimeMinutes: this.elapsedTimeMinutes,
@@ -126,7 +186,7 @@ export default {
 </script>
 
 <template>
-  <gl-card class="gl-border-gray-400 gl-bg-gray-10 gl-mb-3 gl-relative">
+  <gl-card ref="ruleContainer" class="gl-border-gray-400 gl-bg-gray-10 gl-mb-3 gl-relative">
     <gl-button
       v-if="index !== 0"
       category="tertiary"
@@ -138,10 +198,13 @@ export default {
     />
     <gl-form-group :state="isValid" class="gl-mb-0">
       <template #invalid-feedback>
-        <div v-if="!isScheduleValid">
+        <div v-if="!isScheduleValid && !hasFocus">
           {{ $options.i18n.fields.rules.emptyScheduleValidationMsg }}
         </div>
-        <div v-if="!isTimeValid" class="gl-display-inline-block gl-mt-2">
+        <div v-if="!isUserValid && !hasFocus" class="gl-display-inline-block gl-mt-2">
+          {{ $options.i18n.fields.rules.invalidUserValidationMsg }}
+        </div>
+        <div v-if="!isTimeValid && !hasFocus" class="gl-display-inline-block gl-mt-2">
           {{ $options.i18n.fields.rules.invalidTimeValidationMsg }}
         </div>
       </template>
@@ -181,49 +244,53 @@ export default {
           <template #doAction>
             <gl-dropdown
               class="rule-control gl-mx-3"
-              :text="$options.ACTIONS[rule.action]"
+              :text="$options.ACTIONS[action]"
               data-testid="action-dropdown"
             >
               <gl-dropdown-item
                 v-for="(label, ruleAction) in $options.ACTIONS"
                 :key="ruleAction"
-                :is-checked="rule.action === ruleAction"
+                :is-checked="action === ruleAction"
                 is-check-item
+                @click="setAction(ruleAction)"
               >
                 {{ label }}
               </gl-dropdown-item>
             </gl-dropdown>
           </template>
-          <template #schedule>
-            <gl-dropdown
-              :disabled="noSchedules"
-              class="rule-control"
-              :text="scheduleDropdownTitle"
-              data-testid="schedules-dropdown"
-            >
-              <template #button-text>
-                <span :class="{ 'gl-text-gray-400': !oncallScheduleIid }">
-                  {{ scheduleDropdownTitle }}
-                </span>
-              </template>
-              <gl-dropdown-item
-                v-for="schedule in schedules"
-                :key="schedule.iid"
-                :is-checked="schedule.iid === oncallScheduleIid"
-                is-check-item
-                @click="setOncallSchedule(schedule)"
+          <template #scheduleOrUser>
+            <template v-if="isEmailOncallScheduleUserActionSelected">
+              <gl-dropdown
+                :disabled="noSchedules"
+                class="rule-control"
+                :text="scheduleDropdownTitle"
+                data-testid="schedules-dropdown"
               >
-                {{ schedule.name }}
-              </gl-dropdown-item>
-            </gl-dropdown>
-            <gl-icon
-              v-if="noSchedules"
-              v-gl-tooltip
-              :title="$options.i18n.fields.rules.noSchedules"
-              name="information-o"
-              class="gl-text-gray-500 gl-ml-3"
-              data-testid="no-schedules-info-icon"
-            />
+                <template #button-text>
+                  <span :class="{ 'gl-text-gray-400': !oncallScheduleIid }">
+                    {{ scheduleDropdownTitle }}
+                  </span>
+                </template>
+                <gl-dropdown-item
+                  v-for="schedule in schedules"
+                  :key="schedule.iid"
+                  :is-checked="schedule.iid === oncallScheduleIid"
+                  is-check-item
+                  @click="setOncallSchedule(schedule)"
+                >
+                  {{ schedule.name }}
+                </gl-dropdown-item>
+              </gl-dropdown>
+              <gl-icon
+                v-if="noSchedules"
+                v-gl-tooltip
+                :title="$options.i18n.fields.rules.noSchedules"
+                name="information-o"
+                class="gl-text-gray-500 gl-ml-3"
+                data-testid="no-schedules-info-icon"
+              />
+            </template>
+            <user-select v-else :selected-user-name="username" @select-user="setSelectedUser" />
           </template>
         </gl-sprintf>
       </div>
diff --git a/ee/app/assets/javascripts/escalation_policies/components/user_select.vue b/ee/app/assets/javascripts/escalation_policies/components/user_select.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ee16936c99110e6dba9d6bc3bd9ba5bef6c0822a
--- /dev/null
+++ b/ee/app/assets/javascripts/escalation_policies/components/user_select.vue
@@ -0,0 +1,116 @@
+<script>
+import { GlTokenSelector, GlAvatar, GlAvatarLabeled, GlToken } from '@gitlab/ui';
+import searchProjectMembersQuery from '~/graphql_shared/queries/project_user_members_search.query.graphql';
+import { s__, __ } from '~/locale';
+
+export default {
+  components: {
+    GlTokenSelector,
+    GlAvatar,
+    GlAvatarLabeled,
+    GlToken,
+  },
+  inject: ['projectPath'],
+
+  i18n: {
+    placeholder: s__('EscalationPolicies|Search for user'),
+    noResults: __('No matching results'),
+  },
+  props: {
+    selectedUserName: {
+      type: String,
+      required: false,
+      default: null,
+    },
+  },
+  apollo: {
+    users: {
+      query: searchProjectMembersQuery,
+      variables() {
+        return {
+          fullPath: this.projectPath,
+          search: this.search,
+        };
+      },
+      update({ project: { projectMembers: { nodes = [] } = {} } = {} } = {}) {
+        return nodes.filter((x) => x?.user).map(({ user }) => ({ ...user }));
+      },
+      error(error) {
+        this.error = error;
+      },
+      result() {
+        this.setSelectedUser();
+      },
+      debounce: 250,
+    },
+  },
+  data() {
+    return {
+      users: [],
+      selectedUsers: [],
+      search: '',
+    };
+  },
+  computed: {
+    loading() {
+      return this.$apollo.queries.users.loading;
+    },
+    placeholderText() {
+      return this.selectedUsers.length ? '' : this.$options.i18n.placeholder;
+    },
+    user() {
+      return this.selectedUsers[0];
+    },
+  },
+  methods: {
+    filterUsers(searchTerm) {
+      this.search = searchTerm;
+    },
+    emitUserUpdate() {
+      this.$emit('select-user', this.user?.username);
+    },
+    clearSelectedUsers() {
+      this.selectedUsers = [];
+      this.emitUserUpdate();
+    },
+    setSelectedUser() {
+      const selectedUser = this.users.find(({ username }) => username === this.selectedUserName);
+      if (selectedUser) {
+        this.selectedUsers.push(selectedUser);
+      }
+    },
+  },
+};
+</script>
+<template>
+  <div
+    v-if="selectedUsers.length"
+    class="gl-inset-border-1-gray-400 gl-px-3 gl-py-2 gl-rounded-base rule-control"
+  >
+    <gl-token @close="clearSelectedUsers">
+      <gl-avatar :src="user.avatarUrl" :size="16" />
+      {{ user.name }}
+    </gl-token>
+  </div>
+
+  <gl-token-selector
+    v-else
+    ref="tokenSelector"
+    v-model="selectedUsers"
+    :dropdown-items="users"
+    :loading="loading"
+    :placeholder="placeholderText"
+    container-class="rule-control"
+    @text-input="filterUsers"
+    @token-add="emitUserUpdate"
+  >
+    <template #dropdown-item-content="{ dropdownItem }">
+      <gl-avatar-labeled
+        :src="dropdownItem.avatarUrl"
+        :size="32"
+        :label="dropdownItem.name"
+        :sub-label="dropdownItem.username"
+      />
+    </template>
+  </gl-token-selector>
+</template>
diff --git a/ee/app/assets/javascripts/escalation_policies/constants.js b/ee/app/assets/javascripts/escalation_policies/constants.js
index 3386dbb9b448f5e8560c817bddf70e8d0af00c42..2b44f4c4e4c70c11840e15174857c8e2b2a6329d 100644
--- a/ee/app/assets/javascripts/escalation_policies/constants.js
+++ b/ee/app/assets/javascripts/escalation_policies/constants.js
@@ -5,10 +5,12 @@ export const ALERT_STATUSES = {
   RESOLVED: s__('AlertManagement|Resolved'),
 };
 
-export const DEFAULT_ACTION = 'EMAIL_ONCALL_SCHEDULE_USER';
+export const EMAIL_ONCALL_SCHEDULE_USER = 'EMAIL_ONCALL_SCHEDULE_USER';
+export const EMAIL_USER = 'EMAIL_USER';
 
 export const ACTIONS = {
-  [DEFAULT_ACTION]: s__('EscalationPolicies|Email on-call user in schedule'),
+  [EMAIL_ONCALL_SCHEDULE_USER]: s__('EscalationPolicies|Email on-call user in schedule'),
+  [EMAIL_USER]: s__('EscalationPolicies|Email user'),
 };
 
 export const DEFAULT_ESCALATION_RULE = {
diff --git a/ee/app/assets/javascripts/escalation_policies/graphql/fragments/escalation_policy.fragment.graphql b/ee/app/assets/javascripts/escalation_policies/graphql/fragments/escalation_policy.fragment.graphql
index 98f2edd8b5feb1fa3e90e1ebf0717199861145c4..52b9d60ab8ce16ec549cea684f56cbcd51e2cc15 100644
--- a/ee/app/assets/javascripts/escalation_policies/graphql/fragments/escalation_policy.fragment.graphql
+++ b/ee/app/assets/javascripts/escalation_policies/graphql/fragments/escalation_policy.fragment.graphql
@@ -10,5 +10,10 @@ fragment EscalationPolicy on EscalationPolicyType {
       iid
       name
     }
+    user {
+      username
+      name
+      avatarUrl
+    }
   }
 }
diff --git a/ee/app/assets/javascripts/escalation_policies/utils.js b/ee/app/assets/javascripts/escalation_policies/utils.js
index 625031e46584054e850f00c17e976aa3d30677e9..6ff36165ac2a2e592a20546175d875136eb42548 100644
--- a/ee/app/assets/javascripts/escalation_policies/utils.js
+++ b/ee/app/assets/javascripts/escalation_policies/utils.js
@@ -1,3 +1,6 @@
+import { pickBy, isNull, isNaN } from 'lodash';
+import { EMAIL_ONCALL_SCHEDULE_USER, EMAIL_USER } from './constants';
+
 /**
  * Returns `true` for non-empty string, otherwise returns `false`
  * @param {String} name
@@ -15,11 +18,12 @@ export const isNameFieldValid = (name) => {
  * @returns {Array}
  */
 export const getRulesValidationState = (rules) => {
-  return rules.map((rule) => {
-    const minutes = parseInt(rule.elapsedTimeMinutes, 10);
+  return rules.map(({ elapsedTimeMinutes, oncallScheduleIid, username, action }) => {
+    const minutes = parseInt(elapsedTimeMinutes, 10);
     return {
       isTimeValid: minutes >= 0 && minutes <= 1440,
-      isScheduleValid: Boolean(rule.oncallScheduleIid),
+      isScheduleValid: action === EMAIL_ONCALL_SCHEDULE_USER ? Boolean(oncallScheduleIid) : true,
+      isUserValid: action === EMAIL_USER ? Boolean(username) : true,
     };
   });
 };
@@ -30,10 +34,14 @@ export const getRulesValidationState = (rules) => {
  *
  * @returns {Object} rule
  */
-export const serializeRule = ({ elapsedTimeMinutes, ...ruleParams }) => ({
-  ...ruleParams,
-  elapsedTimeSeconds: elapsedTimeMinutes * 60,
-});
+export const serializeRule = ({ elapsedTimeMinutes, ...ruleParams }) => {
+  const params = { ...ruleParams };
+  delete params.action;
+  return {
+    ...params,
+    elapsedTimeSeconds: elapsedTimeMinutes * 60,
+  };
+};
 
 /**
  * Parses a policy by converting elapsed seconds to minutes
@@ -48,3 +56,29 @@ export const parsePolicy = (policy) => ({
     elapsedTimeMinutes: elapsedTimeSeconds / 60,
   })),
 });
+
+/**
+ * Parses a rule for the UI form usage or doe BE params serializing
+ * @param {Array} of transformed rules from BE
+ *
+ * @returns {Array} of rules
+ */
+export const getRules = (rules) => {
+  return rules.map(
+    ({ status, elapsedTimeMinutes, oncallScheduleIid, oncallSchedule, user, username }) => {
+      const actionBasedProps = pickBy(
+        {
+          username: username ?? user?.username,
+          oncallScheduleIid: parseInt(oncallScheduleIid ?? oncallSchedule?.iid, 10),
+        },
+        (prop) => !(isNull(prop) || isNaN(prop)),
+      );
+
+      return {
+        status,
+        elapsedTimeMinutes,
+        ...actionBasedProps,
+      };
+    },
+  );
+};
diff --git a/ee/spec/frontend/escalation_policies/__snapshots__/escalation_policy_spec.js.snap b/ee/spec/frontend/escalation_policies/__snapshots__/escalation_policy_spec.js.snap
index 82b6d25481e48c7573cd78b51a68a73159d61a4b..bd8f82f5e44118525f911d39a49b84c6a8f8d6d6 100644
--- a/ee/spec/frontend/escalation_policies/__snapshots__/escalation_policy_spec.js.snap
+++ b/ee/spec/frontend/escalation_policies/__snapshots__/escalation_policy_spec.js.snap
@@ -24,7 +24,7 @@ exports[`EscalationPolicy renders a policy with rules 1`] = `
         class="gl-border-solid gl-border-1 gl-border-gray-100 gl-rounded-base gl-p-5"
       >
         <div
-          class="gl-mb-5"
+          class="gl-display-flex gl-align-items-center gl-mb-5"
         >
           <gl-icon-stub
             class="gl-mr-3"
@@ -38,7 +38,7 @@ exports[`EscalationPolicy renders a policy with rules 1`] = `
             class="gl-font-weight-bold"
           >
             
-                  1 mins
+                   1 mins
                 
           </span>
            
@@ -57,6 +57,7 @@ exports[`EscalationPolicy renders a policy with rules 1`] = `
           />
            THEN 
                 email on-call user in schedule
+                 
                
           <span
             class="gl-font-weight-bold"
@@ -67,7 +68,7 @@ exports[`EscalationPolicy renders a policy with rules 1`] = `
           </span>
         </div>
         <div
-          class=""
+          class="gl-display-flex gl-align-items-center"
         >
           <gl-icon-stub
             class="gl-mr-3"
@@ -81,7 +82,7 @@ exports[`EscalationPolicy renders a policy with rules 1`] = `
             class="gl-font-weight-bold"
           >
             
-                  2 mins
+                   2 mins
                 
           </span>
            
@@ -99,15 +100,25 @@ exports[`EscalationPolicy renders a policy with rules 1`] = `
             size="16"
           />
            THEN 
-                email on-call user in schedule
+                email user
+                 
                
-          <span
-            class="gl-font-weight-bold"
+          <gl-token-stub
+            variant="default"
+            viewonly="true"
           >
+            <gl-avatar-stub
+              alt="avatar"
+              entityid="0"
+              entityname=""
+              shape="circle"
+              size="16"
+              src="avatar.com/lena.png"
+            />
             
-                  Monitor schedule
+                  Lena
                 
-          </span>
+          </gl-token-stub>
         </div>
       </div>
     </gl-collapse-stub>
diff --git a/ee/spec/frontend/escalation_policies/add_edit_escalation_policy_form_spec.js b/ee/spec/frontend/escalation_policies/add_edit_escalation_policy_form_spec.js
index 988d7c1b8628386da5ee512e0ea6fde48e6a4fc9..88bf2728574140804c5f9f056a45e7b3cc9a6a13 100644
--- a/ee/spec/frontend/escalation_policies/add_edit_escalation_policy_form_spec.js
+++ b/ee/spec/frontend/escalation_policies/add_edit_escalation_policy_form_spec.js
@@ -96,16 +96,28 @@ describe('AddEscalationPolicyForm', () => {
       expect(wrapper.emitted('update-escalation-policy-form')).toBeUndefined();
     });
 
-    it('on rule update emitted should update rules array and emit updates up', () => {
+    it('on rule update emitted should update rules array and emit updates up', async () => {
+      const ruleBeforeUpdate = {
+        status: 'RESOLVED',
+        elapsedTimeMinutes: 3,
+        username: 'user',
+      };
+
+      createComponent({ props: { form: { rules: [ruleBeforeUpdate] } } });
+      await wrapper.vm.$nextTick();
       const updatedRule = {
         status: 'TRIGGERED',
         elapsedTimeMinutes: 3,
         oncallScheduleIid: 2,
       };
       findRules().at(0).vm.$emit('update-escalation-rule', { index: 0, rule: updatedRule });
-      expect(wrapper.emitted('update-escalation-policy-form')[0]).toEqual([
+      const emittedValue = wrapper.emitted('update-escalation-policy-form')[0];
+      expect(emittedValue).toEqual([
         { field: 'rules', value: [expect.objectContaining(updatedRule)] },
       ]);
+      expect(emittedValue).not.toEqual([
+        { field: 'rules', value: [expect.objectContaining(ruleBeforeUpdate)] },
+      ]);
     });
 
     it('on rule removal emitted should update rules array and emit updates up', () => {
diff --git a/ee/spec/frontend/escalation_policies/add_edit_escalation_policy_modal_spec.js b/ee/spec/frontend/escalation_policies/add_edit_escalation_policy_modal_spec.js
index e0c9b75cbb5afacb9c3cc89f6ed2b1ddd1090535..727d45134bce8572169c8df436d3279aaf795f0b 100644
--- a/ee/spec/frontend/escalation_policies/add_edit_escalation_policy_modal_spec.js
+++ b/ee/spec/frontend/escalation_policies/add_edit_escalation_policy_modal_spec.js
@@ -9,6 +9,7 @@ import AddEscalationPolicyModal, {
 import {
   addEscalationPolicyModalId,
   editEscalationPolicyModalId,
+  EMAIL_ONCALL_SCHEDULE_USER,
 } from 'ee/escalation_policies/constants';
 import createEscalationPolicyMutation from 'ee/escalation_policies/graphql/mutations/create_escalation_policy.mutation.graphql';
 import updateEscalationPolicyMutation from 'ee/escalation_policies/graphql/mutations/update_escalation_policy.mutation.graphql';
@@ -267,7 +268,14 @@ describe('AddEditsEscalationPolicyModal', () => {
       });
       form.vm.$emit('update-escalation-policy-form', {
         field: 'rules',
-        value: [{ status: 'RESOLVED', elapsedTimeMinutes: 1, oncallScheduleIid: 1 }],
+        value: [
+          {
+            status: 'RESOLVED',
+            elapsedTimeMinutes: 1,
+            action: EMAIL_ONCALL_SCHEDULE_USER,
+            oncallScheduleIid: 1,
+          },
+        ],
       });
       await wrapper.vm.$nextTick();
       expect(findModal().props('actionPrimary').attributes).toContainEqual({ disabled: false });
diff --git a/ee/spec/frontend/escalation_policies/escalation_rule_spec.js b/ee/spec/frontend/escalation_policies/escalation_rule_spec.js
index 460953c11ea4868cf73e87c17006b574925612ed..2f34aeefe101e874e18aeda5b805eea0b40588a8 100644
--- a/ee/spec/frontend/escalation_policies/escalation_rule_spec.js
+++ b/ee/spec/frontend/escalation_policies/escalation_rule_spec.js
@@ -1,7 +1,14 @@
 import { GlDropdownItem, GlFormGroup, GlSprintf } from '@gitlab/ui';
 import { cloneDeep } from 'lodash';
 import EscalationRule, { i18n } from 'ee/escalation_policies/components/escalation_rule.vue';
-import { DEFAULT_ESCALATION_RULE, ACTIONS, ALERT_STATUSES } from 'ee/escalation_policies/constants';
+import UserSelect from 'ee/escalation_policies/components/user_select.vue';
+import {
+  DEFAULT_ESCALATION_RULE,
+  ACTIONS,
+  ALERT_STATUSES,
+  EMAIL_ONCALL_SCHEDULE_USER,
+  EMAIL_USER,
+} from 'ee/escalation_policies/constants';
 import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
 
 const mockSchedules = [
@@ -11,6 +18,7 @@ const mockSchedules = [
 ];
 
 const emptyScheduleMsg = i18n.fields.rules.emptyScheduleValidationMsg;
+const noUserSelecteddErrorMsg = i18n.fields.rules.invalidUserValidationMsg;
 const invalidTimeMsg = i18n.fields.rules.invalidTimeValidationMsg;
 
 describe('EscalationRule', () => {
@@ -48,7 +56,7 @@ describe('EscalationRule', () => {
 
   const findSchedulesDropdown = () => wrapper.findByTestId('schedules-dropdown');
   const findSchedulesDropdownOptions = () => findSchedulesDropdown().findAll(GlDropdownItem);
-
+  const findUserSelect = () => wrapper.findComponent(UserSelect);
   const findFormGroup = () => wrapper.findComponent(GlFormGroup);
 
   const findNoSchedulesInfoIcon = () => wrapper.findByTestId('no-schedules-info-icon');
@@ -94,25 +102,67 @@ describe('EscalationRule', () => {
       expect(findSchedulesDropdown().attributes('disabled')).toBe('true');
       expect(findNoSchedulesInfoIcon().exists()).toBe(true);
     });
+
+    it('should not render UserSelect when action is EMAIL_ONCALL_SCHEDULE_USER', () => {
+      createComponent({
+        props: {
+          rule: {
+            ...DEFAULT_ESCALATION_RULE,
+            action: EMAIL_ONCALL_SCHEDULE_USER,
+          },
+        },
+      });
+      expect(findUserSelect().exists()).toBe(false);
+    });
+  });
+
+  describe('User select', () => {
+    beforeEach(() => {
+      createComponent({
+        props: {
+          rule: {
+            ...DEFAULT_ESCALATION_RULE,
+            action: EMAIL_USER,
+          },
+        },
+      });
+    });
+
+    it('should render UserSelect when action is EMAIL USER', () => {
+      expect(findUserSelect().exists()).toBe(true);
+    });
+
+    it('should NOT render schedule selection dropdown when action is EMAIL USER', () => {
+      expect(findSchedulesDropdown().exists()).toBe(false);
+    });
   });
 
   describe('Validation', () => {
     describe.each`
-      validationState                                   | formState
-      ${{ isTimeValid: true, isScheduleValid: true }}   | ${'true'}
-      ${{ isTimeValid: false, isScheduleValid: true }}  | ${undefined}
-      ${{ isTimeValid: true, isScheduleValid: false }}  | ${undefined}
-      ${{ isTimeValid: false, isScheduleValid: false }} | ${undefined}
-    `(`when`, ({ validationState, formState }) => {
+      validationState                                                      | formState    | action
+      ${{ isTimeValid: true, isScheduleValid: true, isUserValid: true }}   | ${'true'}    | ${EMAIL_ONCALL_SCHEDULE_USER}
+      ${{ isTimeValid: false, isScheduleValid: true, isUserValid: true }}  | ${undefined} | ${EMAIL_ONCALL_SCHEDULE_USER}
+      ${{ isTimeValid: true, isScheduleValid: false, isUserValid: true }}  | ${undefined} | ${EMAIL_ONCALL_SCHEDULE_USER}
+      ${{ isTimeValid: true, isScheduleValid: true, isUserValid: false }}  | ${undefined} | ${EMAIL_USER}
+      ${{ isTimeValid: false, isScheduleValid: false, isUserValid: true }} | ${undefined} | ${EMAIL_ONCALL_SCHEDULE_USER}
+      ${{ isTimeValid: false, isScheduleValid: true, isUserValid: false }} | ${undefined} | ${EMAIL_USER}
+    `(`when`, ({ validationState, formState, action }) => {
       describe(`elapsed minutes control is ${
         validationState.isTimeValid ? 'valid' : 'invalid'
-      } and schedule control is ${validationState.isScheduleValid ? 'valid' : 'invalid'}`, () => {
+      } and schedule control is ${
+        validationState.isScheduleValid ? 'valid' : 'invalid'
+      } and user control is ${validationState.isUserValid ? 'valid' : 'invalid'}`, () => {
         beforeEach(() => {
           createComponent({
             props: {
               validationState,
+              rule: {
+                ...DEFAULT_ESCALATION_RULE,
+                action,
+              },
             },
           });
+          wrapper.setData({ hasFocus: false });
         });
 
         it(`sets form group validation state to ${formState}`, () => {
@@ -123,17 +173,26 @@ describe('EscalationRule', () => {
           validationState.isTimeValid ? 'not show' : 'show'
         } invalid time error message && does ${
           validationState.isScheduleValid ? 'not show' : 'show'
-        } invalid schedule error message `, () => {
+        } no schedule error message && does ${
+          validationState.isUserValid ? 'not show' : 'show'
+        } no user error message `, () => {
           if (validationState.isTimeValid) {
             expect(findFormGroup().text()).not.toContain(invalidTimeMsg);
           } else {
             expect(findFormGroup().text()).toContain(invalidTimeMsg);
           }
+
           if (validationState.isScheduleValid) {
             expect(findFormGroup().text()).not.toContain(emptyScheduleMsg);
           } else {
             expect(findFormGroup().text()).toContain(emptyScheduleMsg);
           }
+
+          if (validationState.isUserValid) {
+            expect(findFormGroup().text()).not.toContain(noUserSelecteddErrorMsg);
+          } else {
+            expect(findFormGroup().text()).toContain(noUserSelecteddErrorMsg);
+          }
         });
       });
     });
diff --git a/ee/spec/frontend/escalation_policies/mocks/apollo_mock.js b/ee/spec/frontend/escalation_policies/mocks/apollo_mock.js
index d3c5ab93a0a59518c900b28fbe4f1321c24cfecd..0d4b2df91882e05fff0efdc03a4b40048b77d382 100644
--- a/ee/spec/frontend/escalation_policies/mocks/apollo_mock.js
+++ b/ee/spec/frontend/escalation_policies/mocks/apollo_mock.js
@@ -18,6 +18,7 @@ export const getEscalationPoliciesQueryResponse = {
                   name: 'Schedule',
                   __typename: 'IncidentManagementOncallSchedule',
                 },
+                user: null,
                 __typename: 'EscalationRuleType',
               },
             ],
diff --git a/ee/spec/frontend/escalation_policies/mocks/mockPolicies.json b/ee/spec/frontend/escalation_policies/mocks/mockPolicies.json
index 0f0859101aa031076767123dee64fa0b2942b3ab..abbcf83fd59098359bb7daa5052a179088a4749a 100644
--- a/ee/spec/frontend/escalation_policies/mocks/mockPolicies.json
+++ b/ee/spec/frontend/escalation_policies/mocks/mockPolicies.json
@@ -17,9 +17,10 @@
         "id": "gid://gitlab/IncidentManagement::EscalationRule/23",
         "status": "RESOLVED",
         "elapsedTimeSeconds": 120,
-        "oncallSchedule": {
-          "iid": "4",
-          "name": "Monitor schedule"
+        "user": {
+          "username": "sharlatenok",
+          "name": "Lena",
+          "avatarUrl": "avatar.com/lena.png"
         }
       }
     ]
diff --git a/ee/spec/frontend/escalation_policies/user_select_spec.js b/ee/spec/frontend/escalation_policies/user_select_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..cafba643856b996454e19d3c957062e32fb9bd2f
--- /dev/null
+++ b/ee/spec/frontend/escalation_policies/user_select_spec.js
@@ -0,0 +1,97 @@
+import { GlTokenSelector, GlAvatar, GlToken } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import UserSelect from 'ee/escalation_policies/components/user_select.vue';
+
+const mockUsers = [
+  { id: 1, name: 'User 1', avatarUrl: 'avatar.com/user1.png' },
+  { id: 2, name: 'User2', avatarUrl: 'avatar.com/user1.png' },
+];
+
+describe('UserSelect', () => {
+  let wrapper;
+  const projectPath = 'group/project';
+
+  const createComponent = () => {
+    wrapper = shallowMount(UserSelect, {
+      data() {
+        return {
+          users: mockUsers,
+        };
+      },
+      mocks: {
+        $apollo: {
+          queries: {
+            users: { loading: false },
+          },
+        },
+      },
+      stubs: {
+        GlTokenSelector,
+      },
+      provide: {
+        projectPath,
+      },
+    });
+  };
+
+  beforeEach(() => {
+    createComponent();
+  });
+
+  afterEach(() => {
+    wrapper.destroy();
+  });
+
+  const findTokenSelector = () => wrapper.findComponent(GlTokenSelector);
+  const findSelectedUserToken = () => wrapper.findComponent(GlToken);
+  const findAvatar = () => wrapper.findComponent(GlAvatar);
+
+  describe('When no user selected', () => {
+    it('renders token selector and provides it with correct params', () => {
+      const tokenSelector = findTokenSelector();
+      expect(tokenSelector.exists()).toBe(true);
+      expect(tokenSelector.props('dropdownItems')).toEqual(mockUsers);
+      expect(tokenSelector.props('loading')).toEqual(false);
+    });
+
+    it('does not render selected user token', () => {
+      expect(findSelectedUserToken().exists()).toBe(false);
+    });
+  });
+
+  describe('On user selected', () => {
+    it('hides token selector', async () => {
+      const tokenSelector = findTokenSelector();
+      expect(tokenSelector.exists()).toBe(true);
+      tokenSelector.vm.$emit('input', [mockUsers[0]]);
+      await wrapper.vm.$nextTick();
+      expect(tokenSelector.exists()).toBe(false);
+    });
+
+    it('shows selected user token with name and avatar', async () => {
+      const selectedUser = mockUsers[0];
+      findTokenSelector().vm.$emit('input', [selectedUser]);
+      await wrapper.vm.$nextTick();
+      const userToken = findSelectedUserToken();
+      expect(userToken.exists()).toBe(true);
+      expect(userToken.text()).toMatchInterpolatedText(selectedUser.name);
+      const avatar = findAvatar();
+      expect(avatar.exists()).toBe(true);
+      expect(avatar.props('src')).toBe(selectedUser.avatarUrl);
+    });
+  });
+  describe('On user deselected', () => {
+    it('hides selected user token and avatar, shows token selector', async () => {
+      // select user
+      findTokenSelector().vm.$emit('input', [mockUsers[0]]);
+      await wrapper.vm.$nextTick();
+      const userToken = findSelectedUserToken();
+      expect(userToken.exists()).toBe(true);
+      // deselect user
+      userToken.vm.$emit('close');
+      await wrapper.vm.$nextTick();
+      expect(userToken.exists()).toBe(false);
+      expect(findTokenSelector().exists()).toBe(true);
+    });
+  });
+});
diff --git a/ee/spec/frontend/escalation_policies/utils_spec.js b/ee/spec/frontend/escalation_policies/utils_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..06ae2a26390b3e20b8feab22b4f3a292d478913c
--- /dev/null
+++ b/ee/spec/frontend/escalation_policies/utils_spec.js
@@ -0,0 +1,59 @@
+import { EMAIL_ONCALL_SCHEDULE_USER, EMAIL_USER } from 'ee/escalation_policies/constants';
+import * as utils from 'ee/escalation_policies/utils';
+
+describe('Escalation policies utility functions', () => {
+  describe('isNameFieldValid', () => {
+    it('should return `true` when name is valid', () => {
+      expect(utils.isNameFieldValid('policy name')).toBe(true);
+    });
+
+    it('should return `false` otherwise', () => {
+      expect(utils.isNameFieldValid('')).toBe(false);
+      expect(utils.isNameFieldValid(undefined)).toBe(false);
+    });
+  });
+
+  describe('getRulesValidationState', () => {
+    it.each`
+      rules                                                                                                          | validationState
+      ${[{ elapsedTimeMinutes: 10, oncallScheduleIid: 1, username: null, action: EMAIL_ONCALL_SCHEDULE_USER }]}      | ${[{ isTimeValid: true, isScheduleValid: true, isUserValid: true }]}
+      ${[{ elapsedTimeMinutes: 1500, oncallScheduleIid: 1, username: null, action: EMAIL_ONCALL_SCHEDULE_USER }]}    | ${[{ isTimeValid: false, isScheduleValid: true, isUserValid: true }]}
+      ${[{ elapsedTimeMinutes: -2, oncallScheduleIid: null, username: 'user', action: EMAIL_ONCALL_SCHEDULE_USER }]} | ${[{ isTimeValid: false, isScheduleValid: false, isUserValid: true }]}
+      ${[{ elapsedTimeMinutes: 30, oncallScheduleIid: null, username: 'user', action: EMAIL_USER }]}                 | ${[{ isTimeValid: true, isScheduleValid: true, isUserValid: true }]}
+      ${[{ elapsedTimeMinutes: 30, oncallScheduleIid: 1, username: null, action: EMAIL_USER }]}                      | ${[{ isTimeValid: true, isScheduleValid: true, isUserValid: false }]}
+    `('calculates rules validation state', ({ rules, validationState }) => {
+      expect(utils.getRulesValidationState(rules)).toEqual(validationState);
+    });
+  });
+
+  describe('parsePolicy', () => {
+    it('parses a policy by converting elapsed seconds to minutes for ecach rule', () => {
+      const policy = {
+        name: 'policy',
+        rules: [
+          { elapsedTimeSeconds: 600, username: 'user' },
+          { elapsedTimeSeconds: 0, oncallScheduleIid: 1 },
+        ],
+      };
+      expect(utils.parsePolicy(policy)).toEqual({
+        name: 'policy',
+        rules: [
+          { elapsedTimeMinutes: 10, username: 'user' },
+          { elapsedTimeMinutes: 0, oncallScheduleIid: 1 },
+        ],
+      });
+    });
+  });
+
+  describe('getRules', () => {
+    it.each`
+      rules                                                                                                    | transformedRules
+      ${[{ elapsedTimeMinutes: 10, status: 'Acknowledged', oncallScheduleIid: '1', username: null }]}          | ${[{ elapsedTimeMinutes: 10, status: 'Acknowledged', oncallScheduleIid: 1 }]}
+      ${[{ elapsedTimeMinutes: 20, status: 'Resolved', oncallSchedule: { iid: '2' }, username: null }]}        | ${[{ elapsedTimeMinutes: 20, status: 'Resolved', oncallScheduleIid: 2 }]}
+      ${[{ elapsedTimeMinutes: 0, status: 'Resolved', oncallScheduleId: null, username: 'user' }]}             | ${[{ elapsedTimeMinutes: 0, status: 'Resolved', username: 'user' }]}
+      ${[{ elapsedTimeMinutes: 40, status: 'Resolved', oncallScheduleId: null, user: { username: 'user2' } }]} | ${[{ elapsedTimeMinutes: 40, status: 'Resolved', username: 'user2' }]}
+    `('transforms the rules', ({ rules, transformedRules }) => {
+      expect(utils.getRules(rules)).toEqual(transformedRules);
+    });
+  });
+});
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index b0017b7bbfe05244dbab1f600155324724889b6d..8d69e1a35911259f92b7726d0a77051b1e7e0f24 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -13145,6 +13145,9 @@ msgstr ""
 msgid "EscalationPolicies|A schedule is required for adding an escalation policy. Please create an on-call schedule first."
 msgstr ""
 
+msgid "EscalationPolicies|A user is required for adding an escalation policy."
+msgstr ""
+
 msgid "EscalationPolicies|Add an escalation policy"
 msgstr ""
 
@@ -13169,6 +13172,9 @@ msgstr ""
 msgid "EscalationPolicies|Email on-call user in schedule"
 msgstr ""
 
+msgid "EscalationPolicies|Email user"
+msgstr ""
+
 msgid "EscalationPolicies|Escalation policies"
 msgstr ""
 
@@ -13178,7 +13184,7 @@ msgstr ""
 msgid "EscalationPolicies|Failed to load oncall-schedules"
 msgstr ""
 
-msgid "EscalationPolicies|IF alert is not %{alertStatus} in %{minutes} %{then} THEN %{doAction} %{schedule}"
+msgid "EscalationPolicies|IF alert is not %{alertStatus} in %{minutes} %{then} THEN %{doAction} %{scheduleOrUser}"
 msgstr ""
 
 msgid "EscalationPolicies|IF alert is not %{alertStatus} in %{minutes} minutes"
@@ -13193,13 +13199,16 @@ msgstr ""
 msgid "EscalationPolicies|Remove escalation rule"
 msgstr ""
 
+msgid "EscalationPolicies|Search for user"
+msgstr ""
+
 msgid "EscalationPolicies|Select schedule"
 msgstr ""
 
 msgid "EscalationPolicies|Set up escalation policies to define who is paged, and when, in the event the first users paged don't respond."
 msgstr ""
 
-msgid "EscalationPolicies|THEN %{doAction} %{schedule}"
+msgid "EscalationPolicies|THEN %{doAction} %{scheduleOrUser}"
 msgstr ""
 
 msgid "EscalationPolicies|The escalation policy could not be deleted. Please try again."