diff --git a/mail_tracking_mass_mailing/README.rst b/mail_tracking_mass_mailing/README.rst new file mode 100644 index 00000000..9b8bc85e --- /dev/null +++ b/mail_tracking_mass_mailing/README.rst @@ -0,0 +1,77 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +============================== +Mail tracking for mass mailing +============================== + +Links mail statistics objects with mail tracking objects. + + +Installation +============ + +This addon will be automatically installed when 'mail_tracking' and +'mass_mailing' are both installed + + +Usage +===== + +From mail statistic object, you can see: +- Email tracking state +- Email related tracking object +- Email related tracking events + +From mass mailing contact, you can see: +- Email score, in order to clean up your lists from bad score emails + +As a bonus feature, you have a new checkbox 'Avoid resend' in mass mailing, +in order to not send twice the same email to the same recipient. This is very +useful when you want to resend the mass mailing after changing selection +recipients. Notice that recipient selection could be a domain over a model, so +result ids could change over the time. With this flag you can send +the same email several times but only once to each recipient. + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/205/8.0 + + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Pedro M. Baeza +* Antonio Espinosa + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/mail_tracking_mass_mailing/__init__.py b/mail_tracking_mass_mailing/__init__.py new file mode 100644 index 00000000..ba08c9ff --- /dev/null +++ b/mail_tracking_mass_mailing/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# © 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import models +from .hooks import pre_init_hook diff --git a/mail_tracking_mass_mailing/__openerp__.py b/mail_tracking_mass_mailing/__openerp__.py new file mode 100644 index 00000000..0f01f6e0 --- /dev/null +++ b/mail_tracking_mass_mailing/__openerp__.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# © 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + "name": "Mail tracking for mass mailing", + "summary": "Improve mass mailing email tracking", + "version": "8.0.1.0.1", + "category": "Social Network", + "website": "http://www.tecnativa.com", + "author": "Tecnativa, " + "Odoo Community Association (OCA)", + "license": "AGPL-3", + "application": False, + "installable": True, + 'auto_install': True, + "depends": [ + "mass_mailing", + "mail_tracking", + ], + "data": [ + "views/mail_tracking_email_view.xml", + "views/mail_mail_statistics_view.xml", + "views/mail_mass_mailing_view.xml", + "views/mail_mass_mailing_contact_view.xml", + ], + "pre_init_hook": "pre_init_hook", +} diff --git a/mail_tracking_mass_mailing/hooks.py b/mail_tracking_mass_mailing/hooks.py new file mode 100644 index 00000000..b3601a29 --- /dev/null +++ b/mail_tracking_mass_mailing/hooks.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# © 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import logging +try: + from openerp.addons.mail_tracking.hooks import column_add_with_value +except ImportError: + column_add_with_value = False + +_logger = logging.getLogger(__name__) + + +def pre_init_hook(cr): + if column_add_with_value: + _logger.info("Creating mail.mass_mailing.contact.email_score column " + "with value 50.0") + column_add_with_value( + cr, 'mail_mass_mailing_contact', 'email_score', 'double precision', + 50.0) diff --git a/mail_tracking_mass_mailing/i18n/en.po b/mail_tracking_mass_mailing/i18n/en.po new file mode 100644 index 00000000..eb0b0a51 --- /dev/null +++ b/mail_tracking_mass_mailing/i18n/en.po @@ -0,0 +1,130 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mail_tracking_mass_mailing +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 8.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-12 10:39+0000\n" +"PO-Revision-Date: 2016-08-12 10:39+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: mail_tracking_mass_mailing +#: help:mail.mail.statistics,tracking_state:0 +msgid " * The 'Error' status indicates that there was an error when trying to sent the email, for example, 'No valid recipient'\n" +" * The 'Sent' status indicates that message was succesfully sent via outgoing email server (SMTP).\n" +" * The 'Delivered' status indicates that message was succesfully delivered to recipient Mail Exchange (MX) server.\n" +" * The 'Open' status indicates that message was opened or clicked by recipient.\n" +" * The 'Rejected' status indicates that recipient email address is blacklisted by outgoing email server (SMTP). It is recomended to delete this email address.\n" +" * The 'Spam' status indicates that outgoing email server (SMTP) consider this message as spam.\n" +" * The 'Unsubscribed' status indicates that recipient has requested to be unsubscribed from this message.\n" +" * The 'Bounced' status indicates that message was bounced by recipient Mail Exchange (MX) server.\n" +" * The 'Soft bounced' status indicates that message was soft bounced by recipient Mail Exchange (MX) server.\n" +"" +msgstr " * The 'Error' status indicates that there was an error when trying to sent the email, for example, 'No valid recipient'\n" +" * The 'Sent' status indicates that message was succesfully sent via outgoing email server (SMTP).\n" +" * The 'Delivered' status indicates that message was succesfully delivered to recipient Mail Exchange (MX) server.\n" +" * The 'Open' status indicates that message was opened or clicked by recipient.\n" +" * The 'Rejected' status indicates that recipient email address is blacklisted by outgoing email server (SMTP). It is recomended to delete this email address.\n" +" * The 'Spam' status indicates that outgoing email server (SMTP) consider this message as spam.\n" +" * The 'Unsubscribed' status indicates that recipient has requested to be unsubscribed from this message.\n" +" * The 'Bounced' status indicates that message was bounced by recipient Mail Exchange (MX) server.\n" +" * The 'Soft bounced' status indicates that message was soft bounced by recipient Mail Exchange (MX) server.\n" +"" + +#. module: mail_tracking_mass_mailing +#: field:mail.mass_mailing,avoid_resend:0 +msgid "Avoid resend" +msgstr "Avoid resend" + +#. module: mail_tracking_mass_mailing +#: help:mail.mass_mailing,avoid_resend:0 +msgid "Avoid to send this mass mailing email twice to the same recipient" +msgstr "Avoid to send this mass mailing email twice to the same recipient" + +#. module: mail_tracking_mass_mailing +#: view:mail.mail.statistics:mail_tracking_mass_mailing.view_mail_mail_statistics_form +msgid "Country" +msgstr "Country" + +#. module: mail_tracking_mass_mailing +#: model:ir.model,name:mail_tracking_mass_mailing.model_mail_mail_statistics +msgid "Email Statistics" +msgstr "Email Statistics" + +#. module: mail_tracking_mass_mailing +#: field:mail.mass_mailing.contact,email_score:0 +msgid "Email score" +msgstr "Email score" + +#. module: mail_tracking_mass_mailing +#: field:mail.tracking.email,mail_id_int:0 +msgid "Mail ID" +msgstr "Mail ID" + +#. module: mail_tracking_mass_mailing +#: field:mail.tracking.email,mail_stats_id:0 +msgid "Mail statistics" +msgstr "Mail statistics" + +#. module: mail_tracking_mass_mailing +#: field:mail.mail.statistics,mail_tracking_id:0 +msgid "Mail tracking" +msgstr "Mail tracking" + +#. module: mail_tracking_mass_mailing +#: model:ir.model,name:mail_tracking_mass_mailing.model_mail_tracking_email +msgid "MailTracking email" +msgstr "MailTracking email" + +#. module: mail_tracking_mass_mailing +#: model:ir.model,name:mail_tracking_mass_mailing.model_mail_tracking_event +msgid "MailTracking event" +msgstr "MailTracking event" + +#. module: mail_tracking_mass_mailing +#: model:ir.model,name:mail_tracking_mass_mailing.model_mail_mass_mailing +msgid "Mass Mailing" +msgstr "Mass Mailing" + +#. module: mail_tracking_mass_mailing +#: model:ir.model,name:mail_tracking_mass_mailing.model_mail_mass_mailing_contact +msgid "Mass Mailing Contact" +msgstr "Mass Mailing Contact" + +#. module: mail_tracking_mass_mailing +#: field:mail.tracking.email,mass_mailing_id:0 +msgid "Mass mailing" +msgstr "Mass mailing" + +#. module: mail_tracking_mass_mailing +#: model:ir.model,name:mail_tracking_mass_mailing.model_mail_mail +msgid "Outgoing Mails" +msgstr "Outgoing Mails" + +#. module: mail_tracking_mass_mailing +#: field:mail.mail.statistics,tracking_state:0 +msgid "State" +msgstr "State" + +#. module: mail_tracking_mass_mailing +#: field:mail.mass_mailing.contact,tracking_email_ids:0 +msgid "Tracking emails" +msgstr "Tracking emails" + +#. module: mail_tracking_mass_mailing +#: view:mail.mail.statistics:mail_tracking_mass_mailing.view_mail_mail_statistics_form +#: field:mail.mail.statistics,tracking_event_ids:0 +msgid "Tracking events" +msgstr "Tracking events" + +#. module: mail_tracking_mass_mailing +#: view:mail.mail.statistics:mail_tracking_mass_mailing.view_mail_mail_statistics_form +msgid "User agent" +msgstr "User agent" diff --git a/mail_tracking_mass_mailing/i18n/es.po b/mail_tracking_mass_mailing/i18n/es.po new file mode 100644 index 00000000..2d2a085d --- /dev/null +++ b/mail_tracking_mass_mailing/i18n/es.po @@ -0,0 +1,129 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mail_tracking_mass_mailing +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 8.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-12 10:39+0000\n" +"PO-Revision-Date: 2016-08-12 10:39+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: mail_tracking_mass_mailing +#: help:mail.mail.statistics,tracking_state:0 +msgid " * The 'Error' status indicates that there was an error when trying to sent the email, for example, 'No valid recipient'\n" +" * The 'Sent' status indicates that message was succesfully sent via outgoing email server (SMTP).\n" +" * The 'Delivered' status indicates that message was succesfully delivered to recipient Mail Exchange (MX) server.\n" +" * The 'Open' status indicates that message was opened or clicked by recipient.\n" +" * The 'Rejected' status indicates that recipient email address is blacklisted by outgoing email server (SMTP). It is recomended to delete this email address.\n" +" * The 'Spam' status indicates that outgoing email server (SMTP) consider this message as spam.\n" +" * The 'Unsubscribed' status indicates that recipient has requested to be unsubscribed from this message.\n" +" * The 'Bounced' status indicates that message was bounced by recipient Mail Exchange (MX) server.\n" +" * The 'Soft bounced' status indicates that message was soft bounced by recipient Mail Exchange (MX) server.\n" +"" +msgstr " * 'Error' indica que ha habido un error al intentar el envío del email, por ejemplo, 'Email de destino no válido'\n" +" * 'Enviado' indica que el email se ha envíado correctamente al servidor de correo saliente (SMTP)\n" +" * 'Entregado' indica que el email se ha entregado al servidor de correo del destinatario (MX)\n" +" * 'Abierto' indica que el destinatario ha abierto o clicado en el email\n" +" * 'Rechazado' indica que la dirección del destinatario esta en una lista negra en el servidor de correo saliente (SMTP)\n" +" * 'Spam' indica que el servidor de correo saliente (SMTP) considera el email como spam\n" +" * 'Desuscrito' indica que el destinatarios ha solicitado desuscribirse desde este email\n" +" * 'Rebotado' indica que el email no ha sido aceptado por el servidor de correo del destinatario (MX)\n" +" * 'Rebotado leve' indica que el email no ha sido aceptado por motivos temporales por el servidor de correo del destinatario (MX)\n" + +#. module: mail_tracking_mass_mailing +#: field:mail.mass_mailing,avoid_resend:0 +msgid "Avoid resend" +msgstr "Evitar reenvios" + +#. module: mail_tracking_mass_mailing +#: help:mail.mass_mailing,avoid_resend:0 +msgid "Avoid to send this mass mailing email twice to the same recipient" +msgstr "Evitar que se envíe este correo masivo dos veces al mismo destinatario" + +#. module: mail_tracking_mass_mailing +#: view:mail.mail.statistics:mail_tracking_mass_mailing.view_mail_mail_statistics_form +msgid "Country" +msgstr "País" + +#. module: mail_tracking_mass_mailing +#: model:ir.model,name:mail_tracking_mass_mailing.model_mail_mail_statistics +msgid "Email Statistics" +msgstr "Estadísticas del email" + +#. module: mail_tracking_mass_mailing +#: field:mail.mass_mailing.contact,email_score:0 +msgid "Email score" +msgstr "Reputación del email" + +#. module: mail_tracking_mass_mailing +#: field:mail.tracking.email,mail_id_int:0 +msgid "Mail ID" +msgstr "Mail ID" + +#. module: mail_tracking_mass_mailing +#: field:mail.tracking.email,mail_stats_id:0 +msgid "Mail statistics" +msgstr "Estadísticas del email" + +#. module: mail_tracking_mass_mailing +#: field:mail.mail.statistics,mail_tracking_id:0 +msgid "Mail tracking" +msgstr "Seguimiento del email" + +#. module: mail_tracking_mass_mailing +#: model:ir.model,name:mail_tracking_mass_mailing.model_mail_tracking_email +msgid "MailTracking email" +msgstr "MailTracking email" + +#. module: mail_tracking_mass_mailing +#: model:ir.model,name:mail_tracking_mass_mailing.model_mail_tracking_event +msgid "MailTracking event" +msgstr "MailTracking event" + +#. module: mail_tracking_mass_mailing +#: model:ir.model,name:mail_tracking_mass_mailing.model_mail_mass_mailing +msgid "Mass Mailing" +msgstr "Envío masivo" + +#. module: mail_tracking_mass_mailing +#: model:ir.model,name:mail_tracking_mass_mailing.model_mail_mass_mailing_contact +msgid "Mass Mailing Contact" +msgstr "Contacto de envío masivo" + +#. module: mail_tracking_mass_mailing +#: field:mail.tracking.email,mass_mailing_id:0 +msgid "Mass mailing" +msgstr "Envío masivo" + +#. module: mail_tracking_mass_mailing +#: model:ir.model,name:mail_tracking_mass_mailing.model_mail_mail +msgid "Outgoing Mails" +msgstr "Correos salientes" + +#. module: mail_tracking_mass_mailing +#: field:mail.mail.statistics,tracking_state:0 +msgid "State" +msgstr "Estado" + +#. module: mail_tracking_mass_mailing +#: field:mail.mass_mailing.contact,tracking_email_ids:0 +msgid "Tracking emails" +msgstr "Emails de seguimiento" + +#. module: mail_tracking_mass_mailing +#: view:mail.mail.statistics:mail_tracking_mass_mailing.view_mail_mail_statistics_form +#: field:mail.mail.statistics,tracking_event_ids:0 +msgid "Tracking events" +msgstr "Eventos de seguimiento" + +#. module: mail_tracking_mass_mailing +#: view:mail.mail.statistics:mail_tracking_mass_mailing.view_mail_mail_statistics_form +msgid "User agent" +msgstr "Aplicación del usuario" diff --git a/mail_tracking_mass_mailing/models/__init__.py b/mail_tracking_mass_mailing/models/__init__.py new file mode 100644 index 00000000..94fdb95c --- /dev/null +++ b/mail_tracking_mass_mailing/models/__init__.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# © 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import mail_mail +from . import mail_tracking_email +from . import mail_tracking_event +from . import mail_mail_statistics +from . import mail_mass_mailing +from . import mail_mass_mailing_contact diff --git a/mail_tracking_mass_mailing/models/mail_mail.py b/mail_tracking_mass_mailing/models/mail_mail.py new file mode 100644 index 00000000..952d3b42 --- /dev/null +++ b/mail_tracking_mass_mailing/models/mail_mail.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# © 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import models, api + + +class MailMail(models.Model): + _inherit = "mail.mail" + + @api.model + def _tracking_email_prepare(self, mail, partner, email): + res = super(MailMail, self)._tracking_email_prepare( + mail, partner, email) + res['mail_id_int'] = mail.id + res['mass_mailing_id'] = mail.mailing_id.id + res['mail_stats_id'] = mail.statistics_ids[:1].id \ + if mail.statistics_ids else False + return res + + @api.model + def _get_tracking_url(self, mail, partner=None): + # Invalid this tracking image, we have other to do the same + return False diff --git a/mail_tracking_mass_mailing/models/mail_mail_statistics.py b/mail_tracking_mass_mailing/models/mail_mail_statistics.py new file mode 100644 index 00000000..dbcfe9bd --- /dev/null +++ b/mail_tracking_mass_mailing/models/mail_mail_statistics.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# © 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import models, fields + + +class MailMailStatistics(models.Model): + _inherit = "mail.mail.statistics" + + mail_tracking_id = fields.Many2one( + string="Mail tracking", comodel_name='mail.tracking.email', + readonly=True) + tracking_event_ids = fields.One2many( + string="Tracking events", comodel_name='mail.tracking.event', + related='mail_tracking_id.tracking_event_ids', readonly=True) diff --git a/mail_tracking_mass_mailing/models/mail_mass_mailing.py b/mail_tracking_mass_mailing/models/mail_mass_mailing.py new file mode 100644 index 00000000..db32bc8d --- /dev/null +++ b/mail_tracking_mass_mailing/models/mail_mass_mailing.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# © 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import models, api, fields, _ +from openerp.exceptions import Warning as UserError + + +class MailMassMailing(models.Model): + _inherit = 'mail.mass_mailing' + + avoid_resend = fields.Boolean( + string="Avoid resend", + help="Avoid to send this mass mailing email twice " + "to the same recipient") + + @api.model + def get_recipients(self, mailing): + res_ids = super(MailMassMailing, self).get_recipients(mailing) + if mailing.avoid_resend: + already_sent = self.env['mail.mail.statistics'].search([ + ('mass_mailing_id', '=', mailing.id), + ('model', '=', mailing.mailing_model), + ]) + res_ids = list(set(res_ids).difference( + already_sent.mapped('res_id'))) + if not res_ids: + raise UserError(_( + "There is no more recipients to send and 'Avoid resend' " + "option is enabled")) + return res_ids diff --git a/mail_tracking_mass_mailing/models/mail_mass_mailing_contact.py b/mail_tracking_mass_mailing/models/mail_mass_mailing_contact.py new file mode 100644 index 00000000..4edbcf6b --- /dev/null +++ b/mail_tracking_mass_mailing/models/mail_mass_mailing_contact.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# © 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import models, api, fields + + +class MailMassMailingContact(models.Model): + _inherit = 'mail.mass_mailing.contact' + + email_bounced = fields.Boolean(string="Email bounced") + email_score = fields.Float( + string="Email score", readonly=True, store=False, + compute='_compute_email_score') + + @api.multi + @api.depends('email') + def _compute_email_score(self): + for contact in self.filtered('email'): + contact.email_score = self.env['mail.tracking.email'].\ + email_score_from_email(contact.email) + + @api.multi + def email_bounced_set(self, tracking_emails, reason, event=None): + contacts = self.filtered(lambda r: not r.email_bounced) + return contacts.write({'email_bounced': True}) + + @api.multi + def write(self, vals): + email = vals.get('email') + if email is not None: + vals['email_bounced'] = ( + bool(email) and + self.env['mail.tracking.email'].email_is_bounced(email)) + return super(MailMassMailingContact, self).write(vals) diff --git a/mail_tracking_mass_mailing/models/mail_tracking_email.py b/mail_tracking_mass_mailing/models/mail_tracking_email.py new file mode 100644 index 00000000..83aca64f --- /dev/null +++ b/mail_tracking_mass_mailing/models/mail_tracking_email.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# © 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import models, fields, api + + +class MailTrackingEmail(models.Model): + _inherit = "mail.tracking.email" + + mass_mailing_id = fields.Many2one( + string="Mass mailing", comodel_name='mail.mass_mailing', readonly=True) + mail_stats_id = fields.Many2one( + string="Mail statistics", comodel_name='mail.mail.statistics', + readonly=True) + mail_id_int = fields.Integer(string="Mail ID", readonly=True) + + @api.model + def _statistics_link_prepare(self, tracking): + """Inherit this method to link other object to mail.mail.statistics""" + return { + 'mail_tracking_id': tracking.id, + } + + @api.model + def create(self, vals): + tracking = super(MailTrackingEmail, self).create(vals) + # Link mail statistics with this tracking + if tracking.mail_stats_id: + tracking.mail_stats_id.write( + self._statistics_link_prepare(tracking)) + return tracking + + @api.multi + def _contacts_email_bounced_set(self, reason, event=None): + recipients = [] + if event and event.recipient_address: + recipients.append(event.recipient_address) + else: + recipients = list(filter(None, self.mapped('recipient_address'))) + for recipient in recipients: + self.env['mail.mass_mailing.contact'].search([ + ('email', '=ilike', recipient) + ]).email_bounced_set(self, reason, event=event) + + @api.multi + def smtp_error(self, mail_server, smtp_server, exception): + res = super(MailTrackingEmail, self).smtp_error( + mail_server, smtp_server, exception) + self._contacts_email_bounced_set('error') + return res + + @api.multi + def event_create(self, event_type, metadata): + res = super(MailTrackingEmail, self).event_create(event_type, metadata) + if event_type in {'hard_bounce', 'spam', 'reject'}: + self._contacts_email_bounced_set(event_type) + return res diff --git a/mail_tracking_mass_mailing/models/mail_tracking_event.py b/mail_tracking_mass_mailing/models/mail_tracking_event.py new file mode 100644 index 00000000..f18d5166 --- /dev/null +++ b/mail_tracking_mass_mailing/models/mail_tracking_event.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# © 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import api, fields, models + + +class MailTrackingEvent(models.Model): + _inherit = "mail.tracking.event" + + mass_mailing_id = fields.Many2one( + string="Mass mailing", comodel_name='mail.mass_mailing', readonly=True, + related='tracking_email_id.mass_mailing_id', store=True) + + @api.model + def process_open(self, tracking_email, metadata): + res = super(MailTrackingEvent, self).process_open( + tracking_email, metadata) + mail_mail_stats = self.sudo().env['mail.mail.statistics'] + mail_mail_stats.set_opened(mail_mail_ids=[tracking_email.mail_id_int]) + return res + + def _tracking_set_bounce(self, tracking_email, metadata): + mail_mail_stats = self.sudo().env['mail.mail.statistics'] + mail_mail_stats.set_bounced(mail_mail_ids=[tracking_email.mail_id_int]) + + @api.model + def process_hard_bounce(self, tracking_email, metadata): + res = super(MailTrackingEvent, self).process_hard_bounce( + tracking_email, metadata) + self._tracking_set_bounce(tracking_email, metadata) + return res + + @api.model + def process_soft_bounce(self, tracking_email, metadata): + res = super(MailTrackingEvent, self).process_soft_bounce( + tracking_email, metadata) + self._tracking_set_bounce(tracking_email, metadata) + return res + + @api.model + def process_reject(self, tracking_email, metadata): + res = super(MailTrackingEvent, self).process_reject( + tracking_email, metadata) + self._tracking_set_bounce(tracking_email, metadata) + return res + + @api.model + def process_spam(self, tracking_email, metadata): + res = super(MailTrackingEvent, self).process_spam( + tracking_email, metadata) + self._tracking_set_bounce(tracking_email, metadata) + return res diff --git a/mail_tracking_mass_mailing/static/description/icon.png b/mail_tracking_mass_mailing/static/description/icon.png new file mode 100644 index 00000000..3a0328b5 Binary files /dev/null and b/mail_tracking_mass_mailing/static/description/icon.png differ diff --git a/mail_tracking_mass_mailing/tests/__init__.py b/mail_tracking_mass_mailing/tests/__init__.py new file mode 100644 index 00000000..b804cd49 --- /dev/null +++ b/mail_tracking_mass_mailing/tests/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# © 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import test_mass_mailing diff --git a/mail_tracking_mass_mailing/tests/test_mass_mailing.py b/mail_tracking_mass_mailing/tests/test_mass_mailing.py new file mode 100644 index 00000000..d086d736 --- /dev/null +++ b/mail_tracking_mass_mailing/tests/test_mass_mailing.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- +# © 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import mock +from openerp.tests.common import TransactionCase +from openerp.exceptions import Warning as UserError + +mock_send_email = ('openerp.addons.base.ir.ir_mail_server.' + 'ir_mail_server.send_email') + + +class TestMassMailing(TransactionCase): + def setUp(self, *args, **kwargs): + super(TestMassMailing, self).setUp(*args, **kwargs) + self.list = self.env['mail.mass_mailing.list'].create({ + 'name': 'Test mail tracking', + }) + self.contact_a = self.env['mail.mass_mailing.contact'].create({ + 'list_id': self.list.id, + 'name': 'Test contact A', + 'email': 'contact_a@example.com', + }) + self.mailing = self.env['mail.mass_mailing'].create({ + 'name': 'Test subject', + 'email_from': 'from@example.com', + 'mailing_model': 'mail.mass_mailing.contact', + 'mailing_domain': "[('list_id', 'in', [%d]), " + "('opt_out', '=', False)]" % self.list.id, + 'contact_list_ids': [(6, False, [self.list.id])], + 'body_html': '

Test email body

', + 'reply_to_mode': 'email', + }) + + def resend_mass_mailing(self, first, second): + self.mailing.send_mail() + self.assertEqual(len(self.mailing.statistics_ids), first) + self.env['mail.mass_mailing.contact'].create({ + 'list_id': self.list.id, + 'name': 'Test contact B', + 'email': 'contact_b@example.com', + }) + self.mailing.send_mail() + self.assertEqual(len(self.mailing.statistics_ids), second) + + def test_avoid_resend_enable(self): + self.mailing.avoid_resend = True + self.resend_mass_mailing(1, 2) + with self.assertRaises(UserError): + self.mailing.send_mail() + + def test_avoid_resend_disable(self): + self.mailing.avoid_resend = False + self.resend_mass_mailing(1, 3) + + def test_smtp_error(self): + with mock.patch(mock_send_email) as mock_func: + mock_func.side_effect = Warning('Test error') + self.mailing.send_mail() + for stat in self.mailing.statistics_ids: + if stat.mail_mail_id: + stat.mail_mail_id.send() + tracking = self.env['mail.tracking.email'].search([ + ('mail_id_int', '=', stat.mail_mail_id_int), + ]) + self.assertEqual('error', tracking.state) + self.assertEqual('Warning', tracking.error_type) + self.assertEqual('Test error', tracking.error_description) + self.assertTrue(self.contact_a.email_bounced) + + def test_tracking_email_link(self): + self.mailing.send_mail() + for stat in self.mailing.statistics_ids: + if stat.mail_mail_id: + stat.mail_mail_id.send() + tracking_email = self.env['mail.tracking.email'].search([ + ('mail_id_int', '=', stat.mail_mail_id_int), + ]) + self.assertTrue(tracking_email) + self.assertEqual( + tracking_email.mass_mailing_id.id, self.mailing.id) + self.assertEqual(tracking_email.mail_stats_id.id, stat.id) + self.assertEqual(stat.mail_tracking_id.id, tracking_email.id) + # And now open the email + metadata = { + 'ip': '127.0.0.1', + 'user_agent': 'Odoo Test/1.0', + 'os_family': 'linux', + 'ua_family': 'odoo', + } + tracking_email.event_create('open', metadata) + self.assertTrue(stat.opened) + + def _tracking_email_bounce(self, event_type, state): + self.mailing.send_mail() + for stat in self.mailing.statistics_ids: + if stat.mail_mail_id: + stat.mail_mail_id.send() + tracking_email = self.env['mail.tracking.email'].search([ + ('mail_id_int', '=', stat.mail_mail_id_int), + ]) + # And now mark the email as bounce + metadata = { + 'bounce_type': '499', + 'bounce_description': 'Unable to connect to MX servers', + } + tracking_email.event_create(event_type, metadata) + self.assertTrue(stat.bounced) + + def test_tracking_email_hard_bounce(self): + self._tracking_email_bounce('hard_bounce', 'bounced') + + def test_tracking_email_soft_bounce(self): + self._tracking_email_bounce('soft_bounce', 'soft-bounced') + + def test_tracking_email_reject(self): + self._tracking_email_bounce('reject', 'rejected') + + def test_tracking_email_spam(self): + self._tracking_email_bounce('spam', 'spam') + + def test_contact_tracking_emails(self): + self._tracking_email_bounce('hard_bounce', 'bounced') + self.assertTrue(self.contact_a.email_bounced) + self.assertTrue(self.contact_a.email_score < 50.0) + self.contact_a.email = 'other_contact_a@example.com' + self.assertFalse(self.contact_a.email_bounced) + self.assertTrue(self.contact_a.email_score == 50.0) + self.contact_a.email = 'contact_a@example.com' + self.assertTrue(self.contact_a.email_bounced) + self.assertTrue(self.contact_a.email_score < 50.0) diff --git a/mail_tracking_mass_mailing/views/mail_mail_statistics_view.xml b/mail_tracking_mass_mailing/views/mail_mail_statistics_view.xml new file mode 100644 index 00000000..d67ddd21 --- /dev/null +++ b/mail_tracking_mass_mailing/views/mail_mail_statistics_view.xml @@ -0,0 +1,35 @@ + + + + + + + Add tracking email info + mail.mail.statistics + + + + + + + + + + + + diff --git a/mail_tracking_mass_mailing/views/mail_mass_mailing_contact_view.xml b/mail_tracking_mass_mailing/views/mail_mass_mailing_contact_view.xml new file mode 100644 index 00000000..967cabcc --- /dev/null +++ b/mail_tracking_mass_mailing/views/mail_mass_mailing_contact_view.xml @@ -0,0 +1,32 @@ + + + + + + + Add email score and stars + mail.mass_mailing.contact + + + + + + + + + + + Filter bounced contacts + mail.mass_mailing.contact + + + + + + + + + + diff --git a/mail_tracking_mass_mailing/views/mail_mass_mailing_view.xml b/mail_tracking_mass_mailing/views/mail_mass_mailing_view.xml new file mode 100644 index 00000000..b0c0ae4c --- /dev/null +++ b/mail_tracking_mass_mailing/views/mail_mass_mailing_view.xml @@ -0,0 +1,46 @@ + + + + + + + Add avoid resend field + mail.mass_mailing + + + + + + + + + + Mail tracking emails + mail.tracking.email + form + tree,form + [('mass_mailing_id', '!=', False)] + + + + Mail tracking events + mail.tracking.event + form + tree,form + [('mass_mailing_id', '!=', False)] + + + + + + + + + + diff --git a/mail_tracking_mass_mailing/views/mail_tracking_email_view.xml b/mail_tracking_mass_mailing/views/mail_tracking_email_view.xml new file mode 100644 index 00000000..5549650e --- /dev/null +++ b/mail_tracking_mass_mailing/views/mail_tracking_email_view.xml @@ -0,0 +1,20 @@ + + + + + + + Add mass mailing and mail stadistics + mail.tracking.email + + + + + + + + + + +