diff --git a/gajim/common/client.py b/gajim/common/client.py
index 18e94c8a8b4e9ff2cc7267e229dde0a7b6b8adb3..4f1177c31ed19053df45ff37e39014822b8bcd22 100644
--- a/gajim/common/client.py
+++ b/gajim/common/client.py
@@ -182,9 +182,9 @@ def _create_client(self):
 
     def _on_resume_failed(self, _client, _signal_name):
         log.info('Resume failed')
+        self.notify('resume-failed')
         app.nec.push_incoming_event(NetworkEvent(
             'our-show', account=self._account, show='offline'))
-        self.get_module('Chatstate').enabled = False
 
     def _on_resume_successful(self, _client, _signal_name):
         self._set_state(ClientState.CONNECTED)
@@ -278,11 +278,9 @@ def _on_password(password):
             self.notify('state-changed', SimpleClientState.RESUME_IN_PREGRESS)
 
         else:
-            self.get_module('Chatstate').enabled = False
             app.nec.push_incoming_event(NetworkEvent(
                 'our-show', account=self._account, show='offline'))
             self._after_disconnect()
-            self.get_module('Contacts').reset_presence()
             self.notify('state-changed', SimpleClientState.DISCONNECTED)
 
     def _after_disconnect(self):
@@ -305,12 +303,9 @@ def _on_connection_failed(self, _client, _signal_name):
 
     def _on_connected(self, _client, _signal_name):
         self._set_state(ClientState.CONNECTED)
-        self.get_module('MUC').reset_state()
         self.get_module('Discovery').discover_server_info()
         self.get_module('Discovery').discover_account_info()
         self.get_module('Discovery').discover_server_items()
-        self.get_module('Chatstate').enabled = True
-        self.get_module('MAM').reset_state()
 
     def _on_stanza_sent(self, _client, _signal_name, stanza):
         app.nec.push_incoming_event(NetworkEvent('stanza-sent',
diff --git a/gajim/common/helpers.py b/gajim/common/helpers.py
index 13ddcf3c9404b9e67a6470779ea721d9492ea723..d851fe42d9e89a273a828c3869a9611774f2fc51 100644
--- a/gajim/common/helpers.py
+++ b/gajim/common/helpers.py
@@ -1209,10 +1209,13 @@ def multi_connect(self, signal_dict):
             self.connect_signal(signal_name, func)
 
     def notify(self, signal_name, *args, qualifiers=None, **kwargs):
+        signal_callbacks = self._callbacks.get(signal_name)
+        if signal_callbacks is None:
+            return
+
         if self._log is not None:
             self._log.info('Signal: %s', signal_name)
 
-        signal_callbacks = self._callbacks.get(signal_name, {})
         callbacks = signal_callbacks.get(qualifiers, [])
         for func in list(callbacks):
             if func() is None:
diff --git a/gajim/common/modules/chatstates.py b/gajim/common/modules/chatstates.py
index b14678e748a0697b55283dba4f8d11cc860660f0..1059a7216d20821c19df1b2990301af16043522a 100644
--- a/gajim/common/modules/chatstates.py
+++ b/gajim/common/modules/chatstates.py
@@ -29,7 +29,6 @@
 from nbxmpp.const import Chatstate as State
 from gi.repository import GLib
 
-from gajim.common import app
 from gajim.common.structs import OutgoingMessage
 from gajim.common.modules.base import BaseModule
 
@@ -44,7 +43,7 @@
 def ensure_enabled(func):
     @wraps(func)
     def func_wrapper(self, *args, **kwargs):
-        if not self.enabled:
+        if not self._enabled:
             return None
         return func(self, *args, **kwargs)
     return func_wrapper
@@ -76,14 +75,22 @@ def __init__(self, con: ConnectionT) -> None:
         self._blocked = []  # type: List[str]
         self._enabled = False
 
-    @property
-    def enabled(self):
-        return self._enabled
+        self._con.connect_signal('state-changed', self._on_client_state_changed)
+        self._con.connect_signal('resume-failed', self._on_client_resume_failed)
 
-    @enabled.setter
-    def enabled(self, value):
+    def _on_client_resume_failed(self, _client, _signal_name):
+        self._set_enabled(False)
+
+    def _on_client_state_changed(self, _client, _signal_name, state):
+        if state.is_disconnected:
+            self._set_enabled(False)
+        elif state.is_connected:
+            self._set_enabled(True)
+
+    def _set_enabled(self, value):
         if self._enabled == value:
             return
+
         self._log.info('Chatstate module %s',
                        'enabled' if value else 'disabled')
         self._enabled = value
@@ -93,10 +100,7 @@ def enabled(self, value):
                 2, self._check_last_interaction)
         else:
             self.cleanup()
-            self._chatstates = {}
-            self._last_keyboard_activity = {}
-            self._last_mouse_activity = {}
-            self._blocked = []
+            self._con.get_module('Contacts').force_chatstate_update()
 
     @ensure_enabled
     def _presence_received(self,
@@ -171,23 +175,7 @@ def _check_last_interaction(self) -> GLib.SOURCE_CONTINUE:
             if new_chatstate is not None:
                 if self._chatstates.get(jid) != new_chatstate:
                     contact = self._get_contact(jid)
-
-                    # if contact is None:
-                    #     room, nick = app.get_room_and_nick_from_fjid(jid)
-                    #     contact = app.contacts.get_gc_contact(
-                    #         self._account, room, nick)
-                    #     if contact is not None:
-                    #         contact = contact.as_contact()
-                    #     else:
-                    #         # Contact not found, maybe we left the group chat
-                    #         # or the contact was removed from the roster
-                    #         self._log.info(
-                    #             'Contact %s not found, reset chatstate', jid)
-                    #         self._chatstates.pop(jid, None)
-                    #         self._last_mouse_activity.pop(jid, None)
-                    #         self._last_keyboard_activity.pop(jid, None)
-                    #         continue
-                self.set_chatstate(contact, new_chatstate)
+                    self.set_chatstate(contact, new_chatstate)
 
         return GLib.SOURCE_CONTINUE
 
@@ -332,12 +320,19 @@ def remove_delay_timeout(self, contact):
     def remove_all_delay_timeouts(self):
         for timeout in self._delay_timeout_ids.values():
             GLib.source_remove(timeout)
-        self._delay_timeout_ids = {}
+        self._delay_timeout_ids.clear()
 
     def cleanup(self):
         self.remove_all_delay_timeouts()
         if self._timeout_id is not None:
             GLib.source_remove(self._timeout_id)
+            self._timeout_id = None
+
+        self._chatstates.clear()
+        self._remote_chatstate.clear()
+        self._last_keyboard_activity.clear()
+        self._last_mouse_activity.clear()
+        self._blocked = []
 
 
 def get_instance(*args: Any, **kwargs: Any) -> Tuple[Chatstate, str]:
diff --git a/gajim/common/modules/contacts.py b/gajim/common/modules/contacts.py
index 8349d453544f34c1940ced5ea8d3137875f3a4b1..334dd6f4e0007f680dc300449030d3123c3b4a60 100644
--- a/gajim/common/modules/contacts.py
+++ b/gajim/common/modules/contacts.py
@@ -47,6 +47,15 @@ def __init__(self, con: ConnectionT) -> None:
         BaseModule.__init__(self, con)
 
         self._contacts = {}
+        self._con.connect_signal('state-changed', self._on_client_state_changed)
+        self._con.connect_signal('resume-failed', self._on_client_resume_failed)
+
+    def _on_client_resume_failed(self, _client, _signal_name):
+        self._reset_presence()
+
+    def _on_client_state_changed(self, _client, _signal_name, state):
+        if state.is_disconnected:
+            self._reset_presence()
 
     def add_contact(self, jid, groupchat=False):
         if isinstance(jid, str):
@@ -90,12 +99,16 @@ def get_contacts_with_domain(self, domain):
                 contacts.append(contact)
         return contacts
 
-    def reset_presence(self):
+    def _reset_presence(self):
         for contact in self._contacts.values():
             if contact.is_groupchat or contact.is_pm_contact:
                 continue
             contact.update_presence(UNKNOWN_PRESENCE)
 
+    def force_chatstate_update(self):
+        for contact in self._contacts.values():
+            contact.force_chatstate_update()
+
 
 class CommonContact(Observable):
     def __init__(self, logger, jid, account):
@@ -103,6 +116,8 @@ def __init__(self, logger, jid, account):
         self._jid = jid
         self._account = account
 
+        self._resources = {}
+
     def _module(self, name):
         return app.get_client(self._account).get_module(name)
 
@@ -135,6 +150,10 @@ def is_groupchat(self):
     def is_pm_contact(self):
         return False
 
+    def force_chatstate_update(self):
+        for contact in self._resources.values():
+            contact.notify('chatstate-update')
+
 
 class BareContact(CommonContact):
     def __init__(self, logger, jid, account):
@@ -142,7 +161,6 @@ def __init__(self, logger, jid, account):
 
         self.settings = ContactSettings(account, str(jid))
 
-        self._resources = {}
         self._avatar_sha = app.storage.cache.get_contact(jid, 'avatar')
 
     def add_resource(self, resource):
@@ -320,8 +338,6 @@ def __init__(self, logger, jid, account):
 
         self.settings = GroupChatSettings(account, str(jid))
 
-        self._resources = {}
-
     @property
     def is_groupchat(self):
         return True
diff --git a/gajim/common/modules/mam.py b/gajim/common/modules/mam.py
index 67b9828f24ff9caa117d21bc71cdd95ee8633373..aed6620feb47ec8448beddc314105042bb96c765 100644
--- a/gajim/common/modules/mam.py
+++ b/gajim/common/modules/mam.py
@@ -68,6 +68,9 @@ def __init__(self, con):
         # Holds archive jids where catch up was successful
         self._catch_up_finished = []
 
+        self._con.connect_signal('state-changed', self._on_client_state_changed)
+        self._con.connect_signal('resume-failed', self._on_client_resume_failed)
+
     def pass_disco(self, info):
         if Namespace.MAM_2 not in info.features:
             return
@@ -80,7 +83,14 @@ def pass_disco(self, info):
                          account=self._account,
                          feature=Namespace.MAM_2))
 
-    def reset_state(self):
+    def _on_client_resume_failed(self, _client, _signal_name):
+        self._reset_state()
+
+    def _on_client_state_changed(self, _client, _signal_name, state):
+        if state.is_disconnected:
+            self._reset_state()
+
+    def _reset_state(self):
         self._mam_query_ids.clear()
         self._catch_up_finished.clear()
 
diff --git a/gajim/common/modules/muc.py b/gajim/common/modules/muc.py
index 2b427a6f0e2caff0cfd317c3550ca7d7872a0509..b1e05571b88f10cfbb2a259c0e6f75bd24955546 100644
--- a/gajim/common/modules/muc.py
+++ b/gajim/common/modules/muc.py
@@ -30,12 +30,10 @@
 
 from gajim.common import app
 from gajim.common import helpers
-from gajim.common import ged
 from gajim.common.const import KindConstant
 from gajim.common.const import MUCJoinedState
 from gajim.common.helpers import AdditionalDataDict
 from gajim.common.helpers import get_default_muc_config
-from gajim.common.helpers import event_filter
 from gajim.common.helpers import get_group_chat_nick
 from gajim.common.structs import MUCData
 from gajim.common.structs import MUCPresenceData
@@ -109,6 +107,8 @@ def __init__(self, con):
 
         self._con.connect_signal('state-changed',
                                  self._on_client_state_changed)
+        self._con.connect_signal('resume-failed',
+                                 self._on_client_resume_failed)
 
         self._rejoin_muc = set()
         self._join_timeouts = {}
@@ -117,6 +117,14 @@ def __init__(self, con):
         self._joined_users = defaultdict(dict)
         self._mucs = {}
 
+
+    def _on_resume_failed(self, _client, _signal_name):
+        self._reset_presence()
+
+    def _on_state_changed(self, _client, _signal_name, state):
+        if state.is_disconnected:
+            self._reset_presence()
+
     @property
     def supported(self):
         return self._muc_service_jid is not None
@@ -158,7 +166,13 @@ def _set_muc_state(self, room_jid, state):
         contact = self._get_contact(room_jid, groupchat=True)
         contact.notify('state-changed')
 
-    def reset_state(self):
+    def _reset_state(self):
+        for room_jid in list(self._rejoin_timeouts.keys()):
+            self._remove_rejoin_timeout(room_jid)
+
+        for room_jid in list(self._join_timeouts.keys()):
+            self._remove_join_timeout(room_jid)
+
         for muc in self._mucs.values():
             self._joined_users.pop(muc.jid, None)
             self._set_muc_state(muc.jid, MUCJoinedState.NOT_JOINED)
@@ -808,13 +822,10 @@ def invite(self, room, jid, reason=None, continue_=False):
 
     def _on_client_state_changed(self, _client, _signal_name, state):
         if state.is_disconnected:
-            for room_jid in list(self._rejoin_timeouts.keys()):
-                self._remove_rejoin_timeout(room_jid)
-
-            for room_jid in list(self._join_timeouts.keys()):
-                self._remove_join_timeout(room_jid)
+            self._reset_state()
 
-            self.reset_state()
+    def _on_client_resume_failed(self, _client, _signal_name):
+        self._reset_state()
 
 
 def get_instance(*args, **kwargs):