diff --git a/addons/mail/models/mail_blacklist.py b/addons/mail/models/mail_blacklist.py
index 16949dca8e8a5e1397d644f337dec50e0bf78bb3..592928c3a5f91db7f279f8a4ba89bb7de17bc424 100644
--- a/addons/mail/models/mail_blacklist.py
+++ b/addons/mail/models/mail_blacklist.py
@@ -156,3 +156,9 @@ class MailBlackListMixin(models.AbstractModel):
         super(MailBlackListMixin, self)._message_receive_bounce(email, partner)
         for record in self:
             record.message_bounce = record.message_bounce + 1
+
+    def _message_reset_bounce(self, email):
+        """ Override of mail.thread generic method. Purpose is to reset the
+        bounce counter of the record. """
+        super(MailBlackListMixin, self)._message_reset_bounce(email)
+        self.write({'message_bounce': 0})
diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py
index 42916d46b5c66fa9f61ad2e379898b39a351f26d..bdfd821967ccce70a955bd8c9589f53599240faa 100644
--- a/addons/mail/models/mail_thread.py
+++ b/addons/mail/models/mail_thread.py
@@ -815,6 +815,22 @@ class MailThread(models.AbstractModel):
 
         return (model, thread_id, route[2], route[3], route[4])
 
+    @api.model
+    def _routing_reset_bounce(self, email_message, message_dict):
+        """Called by ``message_process`` when a new mail is received from an email address.
+        If the email is related to a partner, we consider that the number of message_bounce
+        is not relevant anymore as the email is valid - as we received an email from this
+        address. The model is here hardcoded because we cannot know with which model the
+        incomming mail match. We consider that if a mail arrives, we have to clear bounce for
+        each model having bounce count.
+
+        :param email_from: email address that sent the incoming email."""
+        valid_email = message_dict['email_from']
+        if valid_email:
+            bl_models = self.env['ir.model'].sudo().search(['&', ('is_mail_blacklist', '=', True), ('model', '!=', 'mail.thread.blacklist')])
+            for model in [bl_model for bl_model in bl_models if bl_model.model in self.env]:  # transient test mode
+                self.env[model.model].sudo().search([('message_bounce', '>', 0), ('email_normalized', '=', valid_email)])._message_reset_bounce(valid_email)
+
     @api.model
     def message_route(self, message, message_dict, model=None, thread_id=None, custom_values=None):
         """ Attempt to figure out the correct target model, thread_id,
@@ -883,6 +899,7 @@ class MailThread(models.AbstractModel):
         #       See http://datatracker.ietf.org/doc/rfc3462/?include_text=1
         #        As all MTA does not respect this RFC (googlemail is one of them),
         #       we also need to verify if the message come from "mailer-daemon"
+        #    If not a bounce: reset bounce information
         if bounce_alias and bounce_alias in email_to_localpart:
             bounce_re = re.compile("%s\+(\d+)-?([\w.]+)?-?(\d+)?" % re.escape(bounce_alias), re.UNICODE)
             bounce_match = bounce_re.search(email_to)
@@ -892,6 +909,7 @@ class MailThread(models.AbstractModel):
         if message.get_content_type() == 'multipart/report' or email_from_localpart == 'mailer-daemon':
             self._routing_handle_bounce(message, message_dict)
             return []
+        self._routing_reset_bounce(message, message_dict)
 
         # 1. Handle reply
         #    if destination = alias with different model -> consider it is a forward and not a reply
@@ -1149,6 +1167,16 @@ class MailThread(models.AbstractModel):
         """
         pass
 
+    def _message_reset_bounce(self, email):
+        """Called by ``message_process`` when an email is considered as not being
+        a bounce. The default behavior is to do nothing. This method is meant to
+        be overridden in various modules to add some specific behavior like
+        blacklist management.
+
+        :param string email: email for which to reset bounce information
+        """
+        pass
+
     def _message_parse_extract_payload_postprocess(self, message, payload_dict):
         """ Perform some cleaning / postprocess in the body and attachments
         extracted from the email. Note that this processing is specific to the
diff --git a/addons/mass_mailing/models/mail_thread.py b/addons/mass_mailing/models/mail_thread.py
index 09c6ba5362613f94734edf18c5efe39d58cf19df..93f657da3cf9e680d8e331004fbd58ae55e3768f 100644
--- a/addons/mass_mailing/models/mail_thread.py
+++ b/addons/mass_mailing/models/mail_thread.py
@@ -50,13 +50,14 @@ class MailThread(models.AbstractModel):
 
         bounced_email = message_dict['bounced_email']
         bounced_msg_id = message_dict['bounced_msg_id']
+        bounced_partner = message_dict['bounced_partner']
 
         if bounced_msg_id:
             self.env['mailing.trace'].set_bounced(mail_message_ids=[bounced_msg_id])
         if bounced_email:
             three_months_ago = fields.Datetime.to_string(datetime.datetime.now() - datetime.timedelta(weeks=13))
             stats = self.env['mailing.trace'].search(['&', ('bounced', '>', three_months_ago), ('email', '=ilike', bounced_email)]).mapped('bounced')
-            if len(stats) >= BLACKLIST_MAX_BOUNCED_LIMIT:
+            if len(stats) >= BLACKLIST_MAX_BOUNCED_LIMIT and (not bounced_partner or bounced_partner.message_bounce >= BLACKLIST_MAX_BOUNCED_LIMIT):
                 if max(stats) > min(stats) + datetime.timedelta(weeks=1):
                     blacklist_rec = self.env['mail.blacklist'].sudo()._add(bounced_email)
                     blacklist_rec._message_log(