diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb
index dcd9011a..223da318 100644
--- a/assets/l10n/intl_en.arb
+++ b/assets/l10n/intl_en.arb
@@ -1561,6 +1561,12 @@
     "type": "text",
     "placeholders": {}
   },
+  "addAccount": "Add account",
+  "editBundlesForAccount": "Edit bundles for this account",
+  "addToBundle": "Add to bundle",
+  "removeFromBundle": "Remove from this bundle",
+  "bundleName": "Bundle name",
+  "enableMultiAccounts": "Enable multi accounts on this device",
   "openInMaps": "Open in maps",
   "@openInMaps": {
     "type": "text",
diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist
index 6b4c0f78..f2872cf4 100644
--- a/ios/Flutter/AppFrameworkInfo.plist
+++ b/ios/Flutter/AppFrameworkInfo.plist
@@ -21,6 +21,6 @@
   CFBundleVersion
   1.0
   MinimumOSVersion
-  8.0
+  9.0
 
 
diff --git a/lib/config/routes.dart b/lib/config/routes.dart
index 813b5eb7..34c302d0 100644
--- a/lib/config/routes.dart
+++ b/lib/config/routes.dart
@@ -217,12 +217,12 @@ class AppRoutes {
           buildTransition: _fadeTransition,
           stackedRoutes: [
             VWidget(
-              path: '/login',
+              path: 'login',
               widget: Login(),
               buildTransition: _fadeTransition,
             ),
             VWidget(
-              path: '/signup',
+              path: 'signup',
               widget: SignupPage(),
               buildTransition: _fadeTransition,
             ),
@@ -296,6 +296,23 @@ class AppRoutes {
               widget: DevicesSettings(),
               buildTransition: _dynamicTransition,
             ),
+            VWidget(
+              path: 'add',
+              widget: HomeserverPicker(),
+              buildTransition: _fadeTransition,
+              stackedRoutes: [
+                VWidget(
+                  path: 'login',
+                  widget: Login(),
+                  buildTransition: _fadeTransition,
+                ),
+                VWidget(
+                  path: 'signup',
+                  widget: SignupPage(),
+                  buildTransition: _fadeTransition,
+                ),
+              ],
+            ),
           ],
         ),
         VWidget(
diff --git a/lib/config/themes.dart b/lib/config/themes.dart
index eae5f19f..f9ab67f7 100644
--- a/lib/config/themes.dart
+++ b/lib/config/themes.dart
@@ -8,6 +8,8 @@ import 'app_config.dart';
 
 abstract class FluffyThemes {
   static const double columnWidth = 360.0;
+  static bool isColumnMode(BuildContext context) =>
+      MediaQuery.of(context).size.width > columnWidth * 2;
 
   static const fallbackTextStyle =
       TextStyle(fontFamily: 'NotoSans', fontFamilyFallback: ['NotoEmoji']);
diff --git a/lib/main.dart b/lib/main.dart
index 0177df27..74b0591e 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -2,7 +2,7 @@
 import 'dart:async';
 
 import 'package:adaptive_theme/adaptive_theme.dart';
-import 'package:matrix/encryption/utils/key_verification.dart';
+import 'package:fluffychat/utils/client_manager.dart';
 import 'package:matrix/matrix.dart';
 import 'package:fluffychat/config/routes.dart';
 import 'package:fluffychat/utils/platform_infos.dart';
@@ -18,8 +18,6 @@ import 'package:future_loading_dialog/future_loading_dialog.dart';
 import 'package:universal_html/html.dart' as html;
 import 'package:vrouter/vrouter.dart';
 
-import 'utils/matrix_sdk_extensions.dart/flutter_matrix_hive_database.dart';
-import 'widgets/layouts/wait_for_login.dart';
 import 'widgets/lock_screen.dart';
 import 'widgets/matrix.dart';
 import 'config/themes.dart';
@@ -35,27 +33,10 @@ void main() async {
   FlutterError.onError = (FlutterErrorDetails details) =>
       Zone.current.handleUncaughtError(details.exception, details.stack);
 
-  final client = Client(
-    PlatformInfos.clientName,
-    enableE2eeRecovery: true,
-    verificationMethods: {
-      KeyVerificationMethod.numbers,
-      if (PlatformInfos.isMobile || PlatformInfos.isLinux)
-        KeyVerificationMethod.emoji,
-    },
-    importantStateEvents: {
-      'im.ponies.room_emotes', // we want emotes to work properly
-    },
-    databaseBuilder: FlutterMatrixHiveStore.hiveDatabaseBuilder,
-    supportedLoginTypes: {
-      AuthenticationTypes.password,
-      if (PlatformInfos.isMobile || PlatformInfos.isWeb) AuthenticationTypes.sso
-    },
-    compute: compute,
-  );
+  final clients = await ClientManager.getClients();
 
   if (PlatformInfos.isMobile) {
-    BackgroundPush.clientOnly(client);
+    BackgroundPush.clientOnly(clients.first);
   }
 
   final queryParameters = {};
@@ -68,24 +49,24 @@ void main() async {
     () => runApp(PlatformInfos.isMobile
         ? AppLock(
             builder: (args) => FluffyChatApp(
-              client: client,
+              clients: clients,
               queryParameters: queryParameters,
             ),
             lockScreen: LockScreen(),
             enabled: false,
           )
-        : FluffyChatApp(client: client, queryParameters: queryParameters)),
+        : FluffyChatApp(clients: clients, queryParameters: queryParameters)),
     SentryController.captureException,
   );
 }
 
 class FluffyChatApp extends StatefulWidget {
   final Widget testWidget;
-  final Client client;
+  final List clients;
   final Map queryParameters;
 
   const FluffyChatApp(
-      {Key key, this.testWidget, @required this.client, this.queryParameters})
+      {Key key, this.testWidget, @required this.clients, this.queryParameters})
       : super(key: key);
 
   /// getInitialLink may rereturn the value multiple times if this view is
@@ -101,7 +82,15 @@ class _FluffyChatAppState extends State {
   final GlobalKey _matrix = GlobalKey();
   GlobalKey _router;
   bool columnMode;
-  String _initialUrl = '/';
+  String _initialUrl;
+
+  @override
+  void initState() {
+    super.initState();
+    _initialUrl =
+        widget.clients.any((client) => client.isLogged()) ? '/rooms' : '/home';
+  }
+
   @override
   Widget build(BuildContext context) {
     return AdaptiveTheme(
@@ -159,8 +148,8 @@ class _FluffyChatAppState extends State {
                 key: _matrix,
                 context: context,
                 router: _router,
-                client: widget.client,
-                child: WaitForInitPage(child),
+                clients: widget.clients,
+                child: child,
               );
             },
           );
diff --git a/lib/pages/chat.dart b/lib/pages/chat.dart
index 2c0e067e..bc983703 100644
--- a/lib/pages/chat.dart
+++ b/lib/pages/chat.dart
@@ -32,6 +32,7 @@ import 'send_location_dialog.dart';
 import 'sticker_picker_dialog.dart';
 import '../utils/matrix_sdk_extensions.dart/filtered_timeline_extension.dart';
 import '../utils/matrix_sdk_extensions.dart/matrix_file_extension.dart';
+import '../utils/account_bundles.dart';
 
 class Chat extends StatefulWidget {
   final Widget sideView;
@@ -45,6 +46,8 @@ class Chat extends StatefulWidget {
 class ChatController extends State {
   Room room;
 
+  Client sendingClient;
+
   Timeline timeline;
 
   MatrixState matrix;
@@ -222,6 +225,14 @@ class ChatController extends State {
 
   TextEditingController sendController = TextEditingController();
 
+  void setSendingClient(Client c) => setState(() {
+        sendingClient = c;
+      });
+
+  void setActiveClient(Client c) => setState(() {
+        Matrix.of(context).setActiveClient(c);
+      });
+
   Future send() async {
     if (sendController.text.trim().isEmpty) return;
     var parseCommands = true;
@@ -447,19 +458,51 @@ class ChatController extends State {
     for (final event in selectedEvents) {
       await showFutureLoadingDialog(
           context: context,
-          future: () =>
-              event.status > 0 ? event.redactEvent() : event.remove());
+          future: () async {
+            if (event.status > 0) {
+              if (event.canRedact) {
+                await event.redactEvent();
+              } else {
+                final client = currentRoomBundle.firstWhere(
+                    (cl) => selectedEvents.first.senderId == cl.userID,
+                    orElse: () => null);
+                if (client == null) {
+                  return;
+                }
+                final room = client.getRoomById(roomId);
+                await Event.fromJson(event.toJson(), room).redactEvent();
+              }
+            } else {
+              await event.remove();
+            }
+          });
     }
     setState(() => selectedEvents.clear());
   }
 
+  List get currentRoomBundle {
+    final clients = matrix.currentBundle;
+    clients.removeWhere((c) => c.getRoomById(roomId) == null);
+    return clients;
+  }
+
   bool get canRedactSelectedEvents {
+    final clients = matrix.currentBundle;
     for (final event in selectedEvents) {
-      if (event.canRedact == false) return false;
+      if (event.canRedact == false &&
+          !(clients.any((cl) => event.senderId == cl.userID))) return false;
     }
     return true;
   }
 
+  bool get canEditSelectedEvents {
+    if (selectedEvents.length != 1 || selectedEvents.first.status < 1) {
+      return false;
+    }
+    return currentRoomBundle
+        .any((cl) => selectedEvents.first.senderId == cl.userID);
+  }
+
   void forwardEventsAction() async {
     if (selectedEvents.length == 1) {
       Matrix.of(context).shareContent = selectedEvents.first.content;
@@ -584,6 +627,13 @@ class ChatController extends State {
       });
 
   void editSelectedEventAction() {
+    final client = currentRoomBundle.firstWhere(
+        (cl) => selectedEvents.first.senderId == cl.userID,
+        orElse: () => null);
+    if (client == null) {
+      return;
+    }
+    setSendingClient(client);
     setState(() {
       pendingText = sendController.text;
       editEvent = selectedEvents.first;
@@ -689,6 +739,19 @@ class ChatController extends State {
   }
 
   void onInputBarChanged(String text) {
+    final clients = currentRoomBundle;
+    for (final client in clients) {
+      final prefix = client.sendPrefix;
+      if ((prefix?.isNotEmpty ?? false) &&
+          text.toLowerCase() == '${prefix.toLowerCase()} ') {
+        setSendingClient(client);
+        setState(() {
+          inputText = '';
+          sendController.text = '';
+        });
+        return;
+      }
+    }
     typingCoolDown?.cancel();
     typingCoolDown = Timer(Duration(seconds: 2), () {
       typingCoolDown = null;
diff --git a/lib/pages/chat_list.dart b/lib/pages/chat_list.dart
index 78411034..e2d51aa5 100644
--- a/lib/pages/chat_list.dart
+++ b/lib/pages/chat_list.dart
@@ -19,6 +19,7 @@ import 'package:uni_links/uni_links.dart';
 import 'package:vrouter/vrouter.dart';
 import '../main.dart';
 import '../widgets/matrix.dart';
+import '../../utils/account_bundles.dart';
 import '../utils/matrix_sdk_extensions.dart/matrix_file_extension.dart';
 import '../utils/url_launcher.dart';
 import 'package:flutter_gen/gen_l10n/l10n.dart';
@@ -409,6 +410,93 @@ class ChatListController extends State {
     }
   }
 
+  void setActiveClient(Client client) {
+    VRouter.of(context).to('/rooms');
+    setState(() {
+      _activeSpaceId = null;
+      selectedRoomIds.clear();
+      Matrix.of(context).setActiveClient(client);
+    });
+  }
+
+  void setActiveBundle(String bundle) => setState(() {
+        _activeSpaceId = null;
+        selectedRoomIds.clear();
+        Matrix.of(context).activeBundle = bundle;
+        if (!Matrix.of(context)
+            .currentBundle
+            .any((client) => client == Matrix.of(context).client)) {
+          Matrix.of(context)
+              .setActiveClient(Matrix.of(context).currentBundle.first);
+        }
+      });
+
+  void editBundlesForAccount(String userId) async {
+    final client = Matrix.of(context)
+        .widget
+        .clients[Matrix.of(context).getClientIndexByMatrixId(userId)];
+    final action = await showConfirmationDialog(
+      context: context,
+      title: L10n.of(context).editBundlesForAccount,
+      actions: [
+        AlertDialogAction(
+          key: EditBundleAction.addToBundle,
+          label: L10n.of(context).addToBundle,
+        ),
+        if (Matrix.of(context).activeBundle != null)
+          AlertDialogAction(
+            key: EditBundleAction.removeFromBundle,
+            label: L10n.of(context).removeFromBundle,
+          ),
+      ],
+    );
+    if (action == null) return;
+    switch (action) {
+      case EditBundleAction.addToBundle:
+        final bundle = await showTextInputDialog(
+            context: context,
+            title: L10n.of(context).bundleName,
+            textFields: [
+              DialogTextField(hintText: L10n.of(context).bundleName)
+            ]);
+        if (bundle.isEmpty && bundle.single.isEmpty) return;
+        await showFutureLoadingDialog(
+          context: context,
+          future: () => client.setAccountBundle(bundle.single),
+        );
+        break;
+      case EditBundleAction.removeFromBundle:
+        await showFutureLoadingDialog(
+          context: context,
+          future: () =>
+              client.removeFromAccountBundle(Matrix.of(context).activeBundle),
+        );
+    }
+  }
+
+  bool get displayBundles =>
+      Matrix.of(context).hasComplexBundles &&
+      Matrix.of(context).accountBundles.keys.length > 1;
+
+  String get secureActiveBundle {
+    if (Matrix.of(context).activeBundle == null ||
+        !Matrix.of(context)
+            .accountBundles
+            .keys
+            .contains(Matrix.of(context).activeBundle)) {
+      return Matrix.of(context).accountBundles.keys.first;
+    }
+    return Matrix.of(context).activeBundle;
+  }
+
+  void resetActiveBundle() {
+    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
+      setState(() {
+        Matrix.of(context).activeBundle = null;
+      });
+    });
+  }
+
   @override
   Widget build(BuildContext context) {
     Matrix.of(context).navigatorContext = context;
@@ -424,3 +512,5 @@ class ChatListController extends State {
     return ChatListView(this);
   }
 }
+
+enum EditBundleAction { addToBundle, removeFromBundle }
diff --git a/lib/pages/homeserver_picker.dart b/lib/pages/homeserver_picker.dart
index 4e485a24..c3fa0df7 100644
--- a/lib/pages/homeserver_picker.dart
+++ b/lib/pages/homeserver_picker.dart
@@ -47,13 +47,13 @@ class HomeserverPickerController extends State {
     showFutureLoadingDialog(
       context: context,
       future: () async {
-        if (Matrix.of(context).client.homeserver == null) {
-          await Matrix.of(context).client.checkHomeserver(
+        if (Matrix.of(context).getLoginClient().homeserver == null) {
+          await Matrix.of(context).getLoginClient().checkHomeserver(
                 await Store()
                     .getItem(HomeserverPickerController.ssoHomeserverKey),
               );
         }
-        await Matrix.of(context).client.login(
+        await Matrix.of(context).getLoginClient().login(
               LoginType.mLoginToken,
               token: token,
               initialDeviceDisplayName: PlatformInfos.clientName,
@@ -117,7 +117,7 @@ class HomeserverPickerController extends State {
         isLoading = true;
       });
       final wellKnown =
-          await Matrix.of(context).client.checkHomeserver(homeserver);
+          await Matrix.of(context).getLoginClient().checkHomeserver(homeserver);
 
       var jitsi = wellKnown?.additionalProperties
           ?.tryGet