diff --git a/src/chat_control.py b/src/chat_control.py
index c41e78c228103c00e3462daba86937bb02b5aed4..1cfc653038b11770ea0d0edb9bb23370e1d83b4e 100644
--- a/src/chat_control.py
+++ b/src/chat_control.py
@@ -2924,12 +2924,12 @@ class ChatControl(ChatControlBase):
                 kind = 'info'
             else:
                 kind = 'print_queue'
-            dm = None
-            if len(data) > 10:
-                dm = data[10]
+            if data[11]:
+                kind = 'out'
+            dm = data[10]
             self.print_conversation(data[0], kind, tim=data[3],
-                    encrypted=data[4], subject=data[1], xhtml=data[7],
-                    displaymarking=dm)
+                encrypted=data[4], subject=data[1], xhtml=data[7],
+                displaymarking=dm)
             if len(data) > 6 and isinstance(data[6], int):
                 message_ids.append(data[6])
 
diff --git a/src/common/config.py b/src/common/config.py
index 9ef46b63728725471ccd76fec2bb72c853764bc4..9c1241c1a36659175405b95a081504089b953d1c 100644
--- a/src/common/config.py
+++ b/src/common/config.py
@@ -381,6 +381,7 @@ class Config:
                     'roster_version': [opt_str, ''],
                     'subscription_request_msg': [opt_str, '', _('Message that is sent to contacts you want to add')],
                     'last_archiving_time': [opt_str, '1970-01-01T00:00:00Z', _('Last time we syncronized with logs from server.')],
+                    'enable_message_carbons': [ opt_bool, False, _('If enabled and if server supports this feature, Gajim will receive messages sent and received by other resources.')],
             }, {}),
             'statusmsg': ({
                     'message': [ opt_str, '' ],
diff --git a/src/common/connection.py b/src/common/connection.py
index c394acc74c05107dc09bd5ee4024a8c9c9caa96f..f49b83c447ecfa1a505eb7008708edf2868055c1 100644
--- a/src/common/connection.py
+++ b/src/common/connection.py
@@ -1709,6 +1709,13 @@ class Connection(CommonConnection, ConnectionHandlers):
                     self.archive_manual_supported = True
                 if common.xmpp.NS_ARCHIVE_PREF in obj.features:
                     self.archive_pref_supported = True
+                if common.xmpp.NS_CARBONS in obj.features and \
+                gajim.config.get_per('accounts', self.name,
+                'enable_message_carbons'):
+                    # Server supports carbons, activate it
+                    iq = common.xmpp.Iq('set')
+                    iq.setTag('enable', namespace=common.xmpp.NS_CARBONS)
+                    self.connection.send(iq)
             if common.xmpp.NS_BYTESTREAM in obj.features and \
             gajim.config.get_per('accounts', self.name, 'use_ft_proxies'):
                 our_fjid = helpers.parse_jid(our_jid + '/' + \
diff --git a/src/common/connection_handlers_events.py b/src/common/connection_handlers_events.py
index 03b04e679ff7fa11f3539cec02d31a4906877ae1..ac14d1aff5db81f6cba53bcaf288c3df553e8556 100644
--- a/src/common/connection_handlers_events.py
+++ b/src/common/connection_handlers_events.py
@@ -977,6 +977,8 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
         self.conn = self.base_event.conn
         self.stanza = self.base_event.stanza
         self.get_id()
+        self.forwarded = False
+        self.sent = False
 
         account = self.conn.name
 
@@ -1013,6 +1015,39 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
                     return
                 self.jid = gajim.get_jid_without_resource(self.fjid)
 
+        forward_tag = self.stanza.getTag('forwarded', namespace=xmpp.NS_FORWARD)
+        # Be sure it comes from one of our resource, else ignore forward element
+        if forward_tag and self.jid == gajim.get_jid_from_account(account):
+            received_tag = forward_tag.getTag('received',
+                namespace=xmpp.NS_CARBONS)
+            sent_tag = forward_tag.getTag('sent', namespace=xmpp.NS_CARBONS)
+            if received_tag:
+                msg = forward_tag.getTag('message')
+                self.stanza = xmpp.Message(node=msg)
+                try:
+                    self.get_jid_resource()
+                except helpers.InvalidFormat:
+                    self.conn.dispatch('ERROR', (_('Invalid Jabber ID'),
+                        _('A message from a non-valid JID arrived, it has been '
+                        'ignored.')))
+                    return
+                self.forwarded = True
+            elif sent_tag:
+                msg = forward_tag.getTag('message')
+                self.stanza = xmpp.Message(node=msg)
+                to = self.stanza.getTo()
+                self.stanza.setTo(self.stanza.getFrom())
+                self.stanza.setFrom(to)
+                try:
+                    self.get_jid_resource()
+                except helpers.InvalidFormat:
+                    self.conn.dispatch('ERROR', (_('Invalid Jabber ID'),
+                        _('A message from a non-valid JID arrived, it has been '
+                        'ignored.')))
+                    return
+                self.forwarded = True
+                self.sent = True
+
         self.enc_tag = self.stanza.getTag('x', namespace=xmpp.NS_ENCRYPTED)
 
         self.invite_tag = None
@@ -1052,7 +1087,8 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
             self.session.last_receive = time_time()
 
         # check if the message is a XEP-0020 feature negotiation request
-        if self.stanza.getTag('feature', namespace=xmpp.NS_FEATURE):
+        if not self.forwarded and self.stanza.getTag('feature',
+        namespace=xmpp.NS_FEATURE):
             if gajim.HAVE_PYCRYPTO:
                 feature = self.stanza.getTag(name='feature',
                     namespace=xmpp.NS_FEATURE)
@@ -1069,7 +1105,8 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
                     self.conn.connection.send(reply)
             return
 
-        if self.stanza.getTag('init', namespace=xmpp.NS_ESESSION_INIT):
+        if not self.forwarded and self.stanza.getTag('init',
+        namespace=xmpp.NS_ESESSION_INIT):
             init = self.stanza.getTag(name='init',
                 namespace=xmpp.NS_ESESSION_INIT)
             form = xmpp.DataForm(node=init.getTag('x'))
@@ -1084,6 +1121,9 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
         xep_200_encrypted = self.stanza.getTag('c',
             namespace=xmpp.NS_STANZA_CRYPTO)
         if xep_200_encrypted:
+            if self.forwarded:
+                # Ignore E2E forwarded encrypted messages
+                return False
             self.encrypted = 'xep200'
 
         return True
@@ -1155,6 +1195,8 @@ class DecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
         self.session = self.msg_obj.session
         self.timestamp = self.msg_obj.timestamp
         self.encrypted = self.msg_obj.encrypted
+        self.forwarded = self.msg_obj.forwarded
+        self.sent = self.msg_obj.sent
         self.popup = False
 
         self.receipt_request_tag = self.stanza.getTag('request',
diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py
index 7816cb76de3c1a78a83c193afa18b414eb37763e..ea9fec9700195f14975365e9d6aa4155223b3ca9 100644
--- a/src/common/xmpp/protocol.py
+++ b/src/common/xmpp/protocol.py
@@ -47,10 +47,11 @@ NS_BOB            = 'urn:xmpp:bob'                                    # XEP-0231
 NS_BOOKMARKS      = 'storage:bookmarks'                               # XEP-0048
 NS_BROWSE         = 'jabber:iq:browse'
 NS_BROWSING       = 'http://jabber.org/protocol/browsing'             # XEP-0195
-NS_BYTESTREAM     = 'http://jabber.org/protocol/bytestreams'          # JEP-0065
-NS_CAPS           = 'http://jabber.org/protocol/caps'                 # JEP-0115
+NS_BYTESTREAM     = 'http://jabber.org/protocol/bytestreams'          # XEP-0065
+NS_CAPS           = 'http://jabber.org/protocol/caps'                 # XEP-0115
 NS_CAPTCHA        = 'urn:xmpp:captcha'                                # XEP-0158
-NS_CHATSTATES     = 'http://jabber.org/protocol/chatstates'           # JEP-0085
+NS_CARBONS        = 'urn:xmpp:carbons:1'                              # XEP-0280
+NS_CHATSTATES     = 'http://jabber.org/protocol/chatstates'           # XEP-0085
 NS_CHATTING       = 'http://jabber.org/protocol/chatting'             # XEP-0194
 NS_CLIENT         = 'jabber:client'
 NS_CONDITIONS     = 'urn:xmpp:muc:conditions:0'                       # XEP-0306
@@ -70,9 +71,10 @@ NS_DISCO_ITEMS    = NS_DISCO + '#items'
 NS_ENCRYPTED      = 'jabber:x:encrypted'                              # XEP-0027
 NS_ESESSION       = 'http://www.xmpp.org/extensions/xep-0116.html#ns'
 NS_ESESSION_INIT  = 'http://www.xmpp.org/extensions/xep-0116.html#ns-init' # XEP-0116
-NS_EVENT                = 'jabber:x:event'                            # XEP-0022
+NS_EVENT          = 'jabber:x:event'                                  # XEP-0022
 NS_FEATURE        = 'http://jabber.org/protocol/feature-neg'
-NS_FILE      = 'http://jabber.org/protocol/si/profile/file-transfer'  # JEP-0096
+NS_FILE           = 'http://jabber.org/protocol/si/profile/file-transfer' # XEP-0096
+NS_FORWARD        = 'urn:xmpp:forward:0'                              # XEP-0297
 NS_GAMING         = 'http://jabber.org/protocol/gaming'               # XEP-0196
 NS_GATEWAY        = 'jabber:iq:gateway'                               # XEP-0100
 NS_GEOLOC         = 'http://jabber.org/protocol/geoloc'               # XEP-0080
@@ -102,7 +104,7 @@ NS_MUC_CONFIG     = NS_MUC + '#roomconfig'
 NS_NICK           = 'http://jabber.org/protocol/nick'                 # XEP-0172
 NS_OFFLINE        = 'http://www.jabber.org/jeps/jep-0030.html'        # XEP-0013
 NS_PHYSLOC        = 'http://jabber.org/protocol/physloc'              # XEP-0112
-NS_PING           = 'urn:xmpp:ping'                                   # SEP-0199
+NS_PING           = 'urn:xmpp:ping'                                   # XEP-0199
 NS_PRESENCE       = 'presence'                                        # Jabberd2
 NS_PRIVACY        = 'jabber:iq:privacy'
 NS_PRIVATE        = 'jabber:iq:private'
@@ -110,7 +112,7 @@ NS_PROFILE        = 'http://jabber.org/protocol/profile'              # XEP-0154
 NS_PUBSUB         = 'http://jabber.org/protocol/pubsub'               # XEP-0060
 NS_PUBSUB_EVENT   = 'http://jabber.org/protocol/pubsub#event'
 NS_PUBSUB_PUBLISH_OPTIONS = NS_PUBSUB + '#publish-options'            # XEP-0060
-NS_PUBSUB_OWNER   = 'http://jabber.org/protocol/pubsub#owner'         # JEP-0060
+NS_PUBSUB_OWNER   = 'http://jabber.org/protocol/pubsub#owner'         # XEP-0060
 NS_REGISTER       = 'jabber:iq:register'
 NS_ROSTER         = 'jabber:iq:roster'
 NS_ROSTERNOTES    = 'storage:rosternotes'
diff --git a/src/roster_window.py b/src/roster_window.py
index 01727a657bd8c1a9bc6da36201bbe3160b3c0f8a..f6464c8354d0d76a336f29879bc5f3f573e42b39 100644
--- a/src/roster_window.py
+++ b/src/roster_window.py
@@ -2584,10 +2584,12 @@ class RosterWindow:
             typ = ''
             if obj.mtype == 'error':
                 typ = 'error'
+            if obj.forwarded and obj.sent:
+                typ = 'out'
 
             obj.session.control.print_conversation(obj.msgtxt, typ,
-            tim=obj.timestamp, encrypted=obj.encrypted, subject=obj.subject,
-            xhtml=obj.xhtml, displaymarking=obj.displaymarking)
+                tim=obj.timestamp, encrypted=obj.encrypted, subject=obj.subject,
+                xhtml=obj.xhtml, displaymarking=obj.displaymarking)
             if obj.msg_id:
                 gajim.logger.set_read_messages([obj.msg_id])
         elif obj.popup:
diff --git a/src/session.py b/src/session.py
index b5bad4b8e3ba822703852728966a5d7f76e5e37c..b8bb4ac307176e006accf2145ddd0e2f4de1cffb 100644
--- a/src/session.py
+++ b/src/session.py
@@ -79,9 +79,13 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
             if not obj.stanza.getTag('body') and obj.chatstate is None:
                 return
 
-            log_type = 'chat_msg_recv'
+            log_type = 'chat_msg'
         else:
-            log_type = 'single_msg_recv'
+            log_type = 'single_msg'
+        end = '_recv'
+        if obj.forwarded and obj.sent:
+            end = '_sent'
+        log_type += end
 
         if self.is_loggable() and obj.msgtxt:
             try:
@@ -113,7 +117,7 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
         # Handle chat states
         contact = gajim.contacts.get_contact(self.conn.name, obj.jid,
             obj.resource)
-        if contact:
+        if contact and (not obj.forwarded or not obj.sent):
             if self.control and self.control.type_id == \
             message_control.TYPE_CHAT:
                 if obj.chatstate is not None:
@@ -237,7 +241,8 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
         if not self.control:
             event = gajim.events.create_event(type_, (obj.msgtxt, obj.subject,
                 obj.mtype, obj.timestamp, obj.encrypted, obj.resource,
-                obj.msg_id, obj.xhtml, self, obj.form_node, obj.displaymarking),
+                obj.msg_id, obj.xhtml, self, obj.form_node, obj.displaymarking,
+                obj.forwarded and obj.sent),
                 show_in_roster=obj.show_in_roster,
                 show_in_systray=obj.show_in_systray)