[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:
David Beguin
2018-08-10 17:46:08 +02:00
parent 59b4836e8d
commit a1d6064dcc
6 changed files with 109 additions and 5 deletions
+17
View File
@@ -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 -2
View File
@@ -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)
+2 -2
View File
@@ -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)