[IMP] mail : auto-blacklist rule when too much email bounced
In order to avoid sending mail indefinitely to a wrong email address, Take the mail statistics for the recipient of the last 3 month : if more than 5 mails bounced (with interval of more than 1 week) the email is blacklisted.
This commit is contained in:
@@ -31,6 +31,7 @@ from odoo.tools.safe_eval import safe_eval
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
BLACKLIST_MAX_BOUNCED_LIMIT = 5
|
||||
|
||||
|
||||
class MailThread(models.AbstractModel):
|
||||
@@ -1452,6 +1453,14 @@ class MailThread(models.AbstractModel):
|
||||
Mail Returned to Sender) is received for an existing thread. The default
|
||||
behavior is to check is an integer ``message_bounce`` column exists.
|
||||
If it is the case, its content is incremented.
|
||||
In addition, an auto blacklist rule check if the email can be blacklisted
|
||||
to avoid sending mails indefinitely to this email address.
|
||||
This rule checks if the email bounced too much. If this is the case,
|
||||
the email address is added to the blacklist in order to avoid continuing
|
||||
to send mass_mail to that email address. If it bounced too much times
|
||||
in the last month and the bounced are at least separated by one week,
|
||||
to avoid blacklist someone because of a temporary mail server error,
|
||||
then the email is considered as invalid and is blacklisted.
|
||||
|
||||
:param mail_id: ID of the sent email that bounced. It may not exist anymore
|
||||
but it could be usefull if the information was kept. This is
|
||||
@@ -1461,6 +1470,14 @@ class MailThread(models.AbstractModel):
|
||||
if 'message_bounce' in self._fields:
|
||||
for record in self:
|
||||
record.message_bounce = record.message_bounce + 1
|
||||
three_months_ago = fields.Datetime.to_string(datetime.datetime.now() - datetime.timedelta(weeks=13))
|
||||
stats = self.env['mail.mail.statistics']\
|
||||
.search(['&', ('bounced', '>', three_months_ago), ('email','=ilike',email)])\
|
||||
.mapped('bounced')
|
||||
if len(stats) >= BLACKLIST_MAX_BOUNCED_LIMIT:
|
||||
if max(stats) > min(stats) + datetime.timedelta(weeks=1):
|
||||
blacklist_rec = self.env['mail.blacklist'].sudo()._add(email)
|
||||
blacklist_rec._message_log('This email has been automatically blacklisted because of too much bounced.')
|
||||
|
||||
def _message_extract_payload_postprocess(self, message, body, attachments):
|
||||
""" Perform some cleaning / postprocess in the body and attachments
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
|
||||
import logging
|
||||
import re
|
||||
import datetime
|
||||
|
||||
from odoo import api, models, tools
|
||||
from odoo import api, models, tools, fields
|
||||
from odoo.tools import decode_smtp_header, decode_message_header
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -49,4 +50,4 @@ class MailThread(models.AbstractModel):
|
||||
default_mass_mailing_name=False,
|
||||
default_mass_mailing_id=False,
|
||||
)
|
||||
return super(MailThread, no_massmail).message_post_with_template(template_id, **kwargs)
|
||||
return super(MailThread, no_massmail).message_post_with_template(template_id, **kwargs)
|
||||
@@ -713,10 +713,10 @@ class MassMailing(models.Model):
|
||||
# avoid loading a large number of records in memory
|
||||
# + use a basic heuristic for extracting emails
|
||||
query = """
|
||||
SELECT lower(substring(%(mail_field)s, '([^ ,;<@]+@[^> ,;]+)'))
|
||||
SELECT lower(substring(t.%(mail_field)s, '([^ ,;<@]+@[^> ,;]+)'))
|
||||
FROM mail_mail_statistics s
|
||||
JOIN %(target)s t ON (s.res_id = t.id)
|
||||
WHERE substring(%(mail_field)s, '([^ ,;<@]+@[^> ,;]+)') IS NOT NULL
|
||||
WHERE substring(t.%(mail_field)s, '([^ ,;<@]+@[^> ,;]+)') IS NOT NULL
|
||||
"""
|
||||
if self.mass_mailing_campaign_id.unique_ab_testing:
|
||||
query +="""
|
||||
|
||||
@@ -57,6 +57,7 @@ class MailMailStats(models.Model):
|
||||
help='Last state update of the mail',
|
||||
store=True)
|
||||
recipient = fields.Char(compute="_compute_recipient")
|
||||
email = fields.Char(string="Recipient email address")
|
||||
|
||||
@api.depends('sent', 'opened', 'clicked', 'replied', 'bounced', 'exception', 'ignored')
|
||||
def _compute_state(self):
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from odoo.tests import common
|
||||
import datetime
|
||||
|
||||
class TestAutoBlacklist(common.TransactionCase):
|
||||
|
||||
def test_mail_bounced_auto_blacklist(self):
|
||||
mass_mailing_contacts = self.env['mail.mass_mailing.contact']
|
||||
mass_mailing = self.env['mail.mass_mailing']
|
||||
mail_blacklist = self.env['mail.blacklist']
|
||||
mail_statistics = self.env['mail.mail.statistics']
|
||||
mail_thread = self.env['mail.thread']
|
||||
|
||||
# create mailing contact record
|
||||
self.mailing_contact_1 = mass_mailing_contacts.create({'name': 'test email 1', 'email': 'test1@email.com'})
|
||||
|
||||
# create bounced history
|
||||
mail_statistics.create({
|
||||
'model': 'mail.mass_mailing.contact',
|
||||
'res_id': self.mailing_contact_1.id,
|
||||
'bounced': datetime.datetime.now() - datetime.timedelta(weeks=2),
|
||||
'email': self.mailing_contact_1.email
|
||||
})
|
||||
self.mailing_contact_1.message_receive_bounce(self.mailing_contact_1.email, self.mailing_contact_1)
|
||||
mail_statistics.create({
|
||||
'model': 'mail.mass_mailing.contact',
|
||||
'res_id': self.mailing_contact_1.id,
|
||||
'bounced': datetime.datetime.now() - datetime.timedelta(weeks=3),
|
||||
'email': self.mailing_contact_1.email
|
||||
})
|
||||
self.mailing_contact_1.message_receive_bounce(self.mailing_contact_1.email, self.mailing_contact_1)
|
||||
mail_statistics.create({
|
||||
'model': 'mail.mass_mailing.contact',
|
||||
'res_id': self.mailing_contact_1.id,
|
||||
'bounced': datetime.datetime.now() - datetime.timedelta(weeks=4),
|
||||
'email': self.mailing_contact_1.email
|
||||
})
|
||||
self.mailing_contact_1.message_receive_bounce(self.mailing_contact_1.email, self.mailing_contact_1)
|
||||
mail_statistics.create({
|
||||
'model': 'mail.mass_mailing.contact',
|
||||
'res_id': self.mailing_contact_1.id,
|
||||
'bounced': datetime.datetime.now() - datetime.timedelta(weeks=5),
|
||||
'email': self.mailing_contact_1.email
|
||||
})
|
||||
self.mailing_contact_1.message_receive_bounce(self.mailing_contact_1.email, self.mailing_contact_1)
|
||||
|
||||
|
||||
# create mass mailing record
|
||||
self.mass_mailing = mass_mailing.create({
|
||||
'name': 'test',
|
||||
'mailing_domain': [('id', 'in',
|
||||
[self.mailing_contact_1.id])],
|
||||
'body_html': 'This is a bounced mail for auto blacklist demo'})
|
||||
self.mass_mailing.put_in_queue()
|
||||
res_ids = self.mass_mailing.get_remaining_recipients()
|
||||
composer_values = {
|
||||
'body': self.mass_mailing.convert_links()[self.mass_mailing.id],
|
||||
'subject': self.mass_mailing.name,
|
||||
'model': self.mass_mailing.mailing_model_real,
|
||||
'email_from': self.mass_mailing.email_from,
|
||||
'composition_mode': 'mass_mail',
|
||||
'mass_mailing_id': self.mass_mailing.id,
|
||||
'mailing_list_ids': [(4, l.id) for l in self.mass_mailing.contact_list_ids],
|
||||
}
|
||||
composer = self.env['mail.compose.message'].with_context(
|
||||
active_ids=res_ids,
|
||||
mass_mailing_seen_list=self.mass_mailing._get_seen_list()
|
||||
).create(composer_values)
|
||||
composer.send_mail()
|
||||
|
||||
mail_statistics.create({
|
||||
'model': 'mail.mass_mailing.contact',
|
||||
'res_id': self.mailing_contact_1.id,
|
||||
'bounced': datetime.datetime.now(),
|
||||
'email': self.mailing_contact_1.email
|
||||
})
|
||||
# call bounced
|
||||
self.mailing_contact_1.message_receive_bounce(self.mailing_contact_1.email, self.mailing_contact_1)
|
||||
|
||||
# check blacklist
|
||||
blacklist_record = mail_blacklist.search([('email', '=', self.mailing_contact_1.email)])
|
||||
self.assertEqual(len(blacklist_record), 1,
|
||||
'The email %s must be blacklisted' % self.mailing_contact_1.email)
|
||||
@@ -73,7 +73,8 @@ class MailComposeMessage(models.TransientModel):
|
||||
stat_vals = {
|
||||
'model': self.model,
|
||||
'res_id': res_id,
|
||||
'mass_mailing_id': mass_mailing.id
|
||||
'mass_mailing_id': mass_mailing.id,
|
||||
'email': mail_to,
|
||||
}
|
||||
if mail_values.get('body_html') and mass_mail_layout:
|
||||
mail_values['body_html'] = mass_mail_layout.render({'body': mail_values['body_html']}, engine='ir.qweb', minimal_qcontext=True)
|
||||
|
||||
Reference in New Issue
Block a user