diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx
index 380f337cda..804b448549 100644
--- a/src/CallHandler.tsx
+++ b/src/CallHandler.tsx
@@ -61,6 +61,7 @@ import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
 import { KIND_CALL_TRANSFER } from "./components/views/dialogs/InviteDialogTypes";
 import { OpenInviteDialogPayload } from "./dispatcher/payloads/OpenInviteDialogPayload";
 import { findDMForUser } from './utils/dm/findDMForUser';
+import { getJoinedNonFunctionalMembers } from './utils/room/getJoinedNonFunctionalMembers';
 
 export const PROTOCOL_PSTN = 'm.protocol.pstn';
 export const PROTOCOL_PSTN_PREFIXED = 'im.vector.protocol.pstn';
@@ -861,7 +862,7 @@ export default class CallHandler extends EventEmitter {
         // We leave the check for whether there's already a call in this room until later,
         // otherwise it can race.
 
-        const members = room.getJoinedMembers();
+        const members = getJoinedNonFunctionalMembers(room);
         if (members.length <= 1) {
             Modal.createDialog(ErrorDialog, {
                 description: _t('You cannot place a call with yourself.'),
diff --git a/src/utils/dm/findDMForUser.ts b/src/utils/dm/findDMForUser.ts
index 7513de88ad..47e3c87a74 100644
--- a/src/utils/dm/findDMForUser.ts
+++ b/src/utils/dm/findDMForUser.ts
@@ -19,6 +19,7 @@ import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
 import DMRoomMap from "../DMRoomMap";
 import { isLocalRoom } from "../localRoom/isLocalRoom";
 import { isJoinedOrNearlyJoined } from "../membership";
+import { getFunctionalMembers } from "../room/getFunctionalMembers";
 
 /**
  * Tries to find a DM room with a specific user.
@@ -39,8 +40,11 @@ export function findDMForUser(client: MatrixClient, userId: string): Room {
         if (r && r.getMyMembership() === "join") {
             if (isLocalRoom(r)) return false;
 
+            const functionalUsers = getFunctionalMembers(r);
             const members = r.currentState.getMembers();
-            const joinedMembers = members.filter(m => isJoinedOrNearlyJoined(m.membership));
+            const joinedMembers = members.filter(
+                m => !functionalUsers.includes(m.userId) && isJoinedOrNearlyJoined(m.membership),
+            );
             const otherMember = joinedMembers.find(m => m.userId === userId);
             return otherMember && joinedMembers.length === 2;
         }
diff --git a/src/utils/room/getFunctionalMembers.ts b/src/utils/room/getFunctionalMembers.ts
new file mode 100644
index 0000000000..4c839b0377
--- /dev/null
+++ b/src/utils/room/getFunctionalMembers.ts
@@ -0,0 +1,27 @@
+/*
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache 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
+
+    http://www.apache.org/licenses/LICENSE-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.
+*/
+
+import { Room, UNSTABLE_ELEMENT_FUNCTIONAL_USERS } from "matrix-js-sdk/src/matrix";
+
+export const getFunctionalMembers = (room: Room): string[] => {
+    const [functionalUsersStateEvent] = room.currentState.getStateEvents(UNSTABLE_ELEMENT_FUNCTIONAL_USERS.name);
+
+    if (Array.isArray(functionalUsersStateEvent?.getContent().service_members)) {
+        return functionalUsersStateEvent.getContent().service_members;
+    }
+
+    return [];
+};
diff --git a/src/utils/room/getJoinedNonFunctionalMembers.ts b/src/utils/room/getJoinedNonFunctionalMembers.ts
new file mode 100644
index 0000000000..912c4bf1f1
--- /dev/null
+++ b/src/utils/room/getJoinedNonFunctionalMembers.ts
@@ -0,0 +1,27 @@
+/*
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache 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
+
+    http://www.apache.org/licenses/LICENSE-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.
+*/
+
+import { Room, RoomMember } from "matrix-js-sdk/src/matrix";
+
+import { getFunctionalMembers } from "./getFunctionalMembers";
+
+/**
+ * Returns all room members that are non-functional (bots etc.).
+ */
+export const getJoinedNonFunctionalMembers = (room: Room): RoomMember[] => {
+    const functionalMembers = getFunctionalMembers(room);
+    return room.getJoinedMembers().filter(m => !functionalMembers.includes(m.userId));
+};
diff --git a/test/CallHandler-test.ts b/test/CallHandler-test.ts
index b7a047a9ef..72b105a160 100644
--- a/test/CallHandler-test.ts
+++ b/test/CallHandler-test.ts
@@ -17,6 +17,7 @@ limitations under the License.
 import { IProtocol } from 'matrix-js-sdk/src/matrix';
 import { CallEvent, CallState, CallType } from 'matrix-js-sdk/src/webrtc/call';
 import EventEmitter from 'events';
+import { mocked } from 'jest-mock';
 
 import CallHandler, {
     CallHandlerEvent, PROTOCOL_PSTN, PROTOCOL_PSTN_PREFIXED, PROTOCOL_SIP_NATIVE, PROTOCOL_SIP_VIRTUAL,
@@ -26,6 +27,11 @@ import { MatrixClientPeg } from '../src/MatrixClientPeg';
 import DMRoomMap from '../src/utils/DMRoomMap';
 import SdkConfig from '../src/SdkConfig';
 import { Action } from "../src/dispatcher/actions";
+import { getFunctionalMembers } from "../src/utils/room/getFunctionalMembers";
+
+jest.mock("../src/utils/room/getFunctionalMembers", () => ({
+    getFunctionalMembers: jest.fn(),
+}));
 
 // The Matrix IDs that the user sees when talking to Alice & Bob
 const NATIVE_ALICE = "@alice:example.org";
@@ -41,6 +47,8 @@ const NATIVE_ROOM_ALICE = "$alice_room:example.org";
 const NATIVE_ROOM_BOB = "$bob_room:example.org";
 const NATIVE_ROOM_CHARLIE = "$charlie_room:example.org";
 
+const FUNCTIONAL_USER = "@bot:example.com";
+
 // The room we use to talk to virtual Bob (but that the user does not see)
 // Bob has a virtual room, but Alice doesn't
 const VIRTUAL_ROOM_BOB = "$virtual_bob_room:example.org";
@@ -69,9 +77,17 @@ function mkStubDM(roomId, userId) {
             getAvatarUrl: () => 'mxc://avatar.url/image.png',
             getMxcAvatarUrl: () => 'mxc://avatar.url/image.png',
         },
+        {
+            userId: FUNCTIONAL_USER,
+            name: 'Bot user',
+            rawDisplayName: 'Bot user',
+            roomId: roomId,
+            membership: 'join',
+            getAvatarUrl: () => 'mxc://avatar.url/image.png',
+            getMxcAvatarUrl: () => 'mxc://avatar.url/image.png',
+        },
     ]);
     room.currentState.getMembers = room.getJoinedMembers;
-
     return room;
 }
 
@@ -132,6 +148,10 @@ describe('CallHandler', () => {
         callHandler = new CallHandler();
         callHandler.start();
 
+        mocked(getFunctionalMembers).mockReturnValue([
+            FUNCTIONAL_USER,
+        ]);
+
         const nativeRoomAlice = mkStubDM(NATIVE_ROOM_ALICE, NATIVE_ALICE);
         const nativeRoomBob = mkStubDM(NATIVE_ROOM_BOB, NATIVE_BOB);
         const nativeRoomCharie = mkStubDM(NATIVE_ROOM_CHARLIE, NATIVE_CHARLIE);
diff --git a/test/utils/dm/findDMForUser-test.ts b/test/utils/dm/findDMForUser-test.ts
index a3dda43c5c..c1acad95e7 100644
--- a/test/utils/dm/findDMForUser-test.ts
+++ b/test/utils/dm/findDMForUser-test.ts
@@ -17,56 +17,112 @@ limitations under the License.
 import { mocked } from "jest-mock";
 import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
 
-import { DirectoryMember, ThreepidMember } from "../../../src/utils/direct-messages";
-import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
 import DMRoomMap from "../../../src/utils/DMRoomMap";
-import { createTestClient } from "../../test-utils";
-import { findDMRoom } from "../../../src/utils/dm/findDMRoom";
+import { createTestClient, makeMembershipEvent } from "../../test-utils";
+import { LocalRoom } from "../../../src/models/LocalRoom";
 import { findDMForUser } from "../../../src/utils/dm/findDMForUser";
+import { getFunctionalMembers } from "../../../src/utils/room/getFunctionalMembers";
 
-jest.mock("../../../src/utils/dm/findDMForUser", () => ({
-    findDMForUser: jest.fn(),
+jest.mock("../../../src/utils/room/getFunctionalMembers", () => ({
+    getFunctionalMembers: jest.fn(),
 }));
 
-describe("findDMRoom", () => {
+describe("findDMForUser", () => {
     const userId1 = "@user1:example.com";
-    const member1 = new DirectoryMember({ user_id: userId1 });
-    const member2 = new ThreepidMember("user2");
-    let mockClient: MatrixClient;
+    const userId2 = "@user2:example.com";
+    const botId = "@bot:example.com";
     let room1: Room;
+    let room2: LocalRoom;
+    let room3: Room;
+    let room4: Room;
+    let room5: Room;
     let dmRoomMap: DMRoomMap;
+    let mockClient: MatrixClient;
 
     beforeEach(() => {
         mockClient = createTestClient();
-        jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient);
+
+        // always return the bot user as functional member
+        mocked(getFunctionalMembers).mockReturnValue([botId]);
 
         room1 = new Room("!room1:example.com", mockClient, userId1);
         room1.getMyMembership = () => "join";
+        room1.currentState.setStateEvents([
+            makeMembershipEvent(room1.roomId, userId1, "join"),
+            makeMembershipEvent(room1.roomId, userId2, "join"),
+        ]);
+
+        // this should not be a DM room because it is a local room
+        room2 = new LocalRoom("!room2:example.com", mockClient, userId1);
+        room2.getMyMembership = () => "join";
+        room2.getLastActiveTimestamp = () => 100;
+
+        room3 = new Room("!room3:example.com", mockClient, userId1);
+        room3.getMyMembership = () => "join";
+        room3.currentState.setStateEvents([
+            makeMembershipEvent(room3.roomId, userId1, "join"),
+            makeMembershipEvent(room3.roomId, userId2, "join"),
+            // Adding the bot user here. Should be excluded when determining if the room is a DM.
+            makeMembershipEvent(room3.roomId, botId, "join"),
+        ]);
+
+        // this should not be a DM room because it has only one joined user
+        room4 = new Room("!room4:example.com", mockClient, userId1);
+        room4.getMyMembership = () => "join";
+        room4.currentState.setStateEvents([
+            makeMembershipEvent(room4.roomId, userId1, "invite"),
+            makeMembershipEvent(room4.roomId, userId2, "join"),
+        ]);
+
+        // this should not be a DM room because it has no users
+        room5 = new Room("!room5:example.com", mockClient, userId1);
+        room5.getLastActiveTimestamp = () => 100;
+
+        mocked(mockClient.getRoom).mockImplementation((roomId: string) => {
+            return {
+                [room1.roomId]: room1,
+                [room2.roomId]: room2,
+                [room3.roomId]: room3,
+                [room4.roomId]: room4,
+                [room5.roomId]: room5,
+            }[roomId];
+        });
 
         dmRoomMap = {
             getDMRoomForIdentifiers: jest.fn(),
             getDMRoomsForUserId: jest.fn(),
         } as unknown as DMRoomMap;
         jest.spyOn(DMRoomMap, "shared").mockReturnValue(dmRoomMap);
+        mocked(dmRoomMap.getDMRoomsForUserId).mockReturnValue([
+            room1.roomId,
+            room2.roomId,
+            room3.roomId,
+            room4.roomId,
+            room5.roomId,
+        ]);
     });
 
-    it("should return the room for a single target with a room", () => {
-        mocked(findDMForUser).mockReturnValue(room1);
-        expect(findDMRoom(mockClient, [member1])).toBe(room1);
+    describe("for an empty DM room list", () => {
+        beforeEach(() => {
+            mocked(dmRoomMap.getDMRoomsForUserId).mockReturnValue([]);
+        });
+
+        it("should return undefined", () => {
+            expect(findDMForUser(mockClient, userId1)).toBeUndefined();
+        });
     });
 
-    it("should return null for a single target without a room", () => {
-        mocked(findDMForUser).mockReturnValue(null);
-        expect(findDMRoom(mockClient, [member1])).toBeNull();
+    it("should find a room ordered by last activity 1", () => {
+        room1.getLastActiveTimestamp = () => 2;
+        room3.getLastActiveTimestamp = () => 1;
+
+        expect(findDMForUser(mockClient, userId1)).toBe(room1);
     });
 
-    it("should return the room for 2 targets with a room", () => {
-        mocked(dmRoomMap.getDMRoomForIdentifiers).mockReturnValue(room1);
-        expect(findDMRoom(mockClient, [member1, member2])).toBe(room1);
-    });
+    it("should find a room ordered by last activity 2", () => {
+        room1.getLastActiveTimestamp = () => 1;
+        room3.getLastActiveTimestamp = () => 2;
 
-    it("should return null for 2 targets without a room", () => {
-        mocked(dmRoomMap.getDMRoomForIdentifiers).mockReturnValue(null);
-        expect(findDMRoom(mockClient, [member1, member2])).toBeNull();
+        expect(findDMForUser(mockClient, userId1)).toBe(room3);
     });
 });
diff --git a/test/utils/room/getJoinedNonFunctionalMembers-test.ts b/test/utils/room/getJoinedNonFunctionalMembers-test.ts
new file mode 100644
index 0000000000..7973f6f384
--- /dev/null
+++ b/test/utils/room/getJoinedNonFunctionalMembers-test.ts
@@ -0,0 +1,97 @@
+/*
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache 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
+
+    http://www.apache.org/licenses/LICENSE-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.
+*/
+
+import { mocked } from "jest-mock";
+import { MatrixClient, Room, RoomMember } from "matrix-js-sdk/src/matrix";
+
+import { getFunctionalMembers } from "../../../src/utils/room/getFunctionalMembers";
+import { getJoinedNonFunctionalMembers } from "../../../src/utils/room/getJoinedNonFunctionalMembers";
+
+jest.mock("../../../src/utils/room/getFunctionalMembers", () => ({
+    getFunctionalMembers: jest.fn(),
+}));
+
+describe("getJoinedNonFunctionalMembers", () => {
+    let room: Room;
+    let roomMember1: RoomMember;
+    let roomMember2: RoomMember;
+
+    beforeEach(() => {
+        room = new Room("!room:example.com", {} as unknown as MatrixClient, "@user:example.com");
+        room.getJoinedMembers = jest.fn();
+
+        roomMember1 = new RoomMember(room.roomId, "@user1:example.com");
+        roomMember2 = new RoomMember(room.roomId, "@user2:example.com");
+    });
+
+    describe("if there are no members", () => {
+        beforeEach(() => {
+            mocked(room.getJoinedMembers).mockReturnValue([]);
+            mocked(getFunctionalMembers).mockReturnValue([]);
+        });
+
+        it("should return an empty list", () => {
+            expect(getJoinedNonFunctionalMembers(room)).toHaveLength(0);
+        });
+    });
+
+    describe("if there are only regular room members", () => {
+        beforeEach(() => {
+            mocked(room.getJoinedMembers).mockReturnValue([
+                roomMember1,
+                roomMember2,
+            ]);
+            mocked(getFunctionalMembers).mockReturnValue([]);
+        });
+
+        it("should return the room members", () => {
+            const members = getJoinedNonFunctionalMembers(room);
+            expect(members).toContain(roomMember1);
+            expect(members).toContain(roomMember2);
+        });
+    });
+
+    describe("if there are only functional room members", () => {
+        beforeEach(() => {
+            mocked(room.getJoinedMembers).mockReturnValue([]);
+            mocked(getFunctionalMembers).mockReturnValue([
+                "@functional:example.com",
+            ]);
+        });
+
+        it("should return an empty list", () => {
+            expect(getJoinedNonFunctionalMembers(room)).toHaveLength(0);
+        });
+    });
+
+    describe("if there are some functional room members", () => {
+        beforeEach(() => {
+            mocked(room.getJoinedMembers).mockReturnValue([
+                roomMember1,
+                roomMember2,
+            ]);
+            mocked(getFunctionalMembers).mockReturnValue([
+                roomMember1.userId,
+            ]);
+        });
+
+        it("should only return the non-functional members", () => {
+            const members = getJoinedNonFunctionalMembers(room);
+            expect(members).not.toContain(roomMember1);
+            expect(members).toContain(roomMember2);
+        });
+    });
+});
diff --git a/test/utils/room/getRoomFunctionalMembers-test.ts b/test/utils/room/getRoomFunctionalMembers-test.ts
new file mode 100644
index 0000000000..bcbba0f226
--- /dev/null
+++ b/test/utils/room/getRoomFunctionalMembers-test.ts
@@ -0,0 +1,53 @@
+/*
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache 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
+
+    http://www.apache.org/licenses/LICENSE-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.
+*/
+
+import { Room, UNSTABLE_ELEMENT_FUNCTIONAL_USERS } from "matrix-js-sdk/src/matrix";
+
+import { getFunctionalMembers } from "../../../src/utils/room/getFunctionalMembers";
+import { createTestClient, mkEvent } from "../../test-utils";
+
+describe("getRoomFunctionalMembers", () => {
+    const client = createTestClient();
+    const room = new Room("!room:example.com", client, client.getUserId());
+
+    it("should return an empty array if no functional members state event exists", () => {
+        expect(getFunctionalMembers(room)).toHaveLength(0);
+    });
+
+    it("should return an empty array if functional members state event does not have a service_members field", () => {
+        room.currentState.setStateEvents([mkEvent({
+            event: true,
+            type: UNSTABLE_ELEMENT_FUNCTIONAL_USERS.name,
+            user: "@user:example.com)",
+            room: room.roomId,
+            skey: "",
+            content: {},
+        })]);
+        expect(getFunctionalMembers(room)).toHaveLength(0);
+    });
+
+    it("should return service_members field of the functional users state event", () => {
+        room.currentState.setStateEvents([mkEvent({
+            event: true,
+            type: UNSTABLE_ELEMENT_FUNCTIONAL_USERS.name,
+            user: "@user:example.com)",
+            room: room.roomId,
+            skey: "",
+            content: { service_members: ["@user:example.com"] },
+        })]);
+        expect(getFunctionalMembers(room)).toEqual(["@user:example.com"]);
+    });
+});