From e739bd557ccfb9d36b1cd1aa23fbe9f973e238b2 Mon Sep 17 00:00:00 2001 From: Antonio Espinosa Date: Mon, 19 Sep 2016 09:53:42 +0200 Subject: [PATCH 01/44] OCA Transbot updated translations from Transifex --- mail_tracking_mailgun/i18n/es.po | 24 ++++++++++++++++++++++++ mail_tracking_mailgun/i18n/fr.po | 24 ++++++++++++++++++++++++ mail_tracking_mailgun/i18n/sl.po | 24 ++++++++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 mail_tracking_mailgun/i18n/es.po create mode 100644 mail_tracking_mailgun/i18n/fr.po create mode 100644 mail_tracking_mailgun/i18n/sl.po diff --git a/mail_tracking_mailgun/i18n/es.po b/mail_tracking_mailgun/i18n/es.po new file mode 100644 index 0000000..9b6e844 --- /dev/null +++ b/mail_tracking_mailgun/i18n/es.po @@ -0,0 +1,24 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mail_tracking_mailgun +# +# Translators: +# OCA Transbot , 2016 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 8.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-02 02:43+0000\n" +"PO-Revision-Date: 2016-09-02 02:43+0000\n" +"Last-Translator: OCA Transbot , 2016\n" +"Language-Team: Spanish (https://www.transifex.com/oca/teams/23907/es/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: es\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: mail_tracking_mailgun +#: model:ir.model,name:mail_tracking_mailgun.model_mail_tracking_email +msgid "MailTracking email" +msgstr "MailTracking email" diff --git a/mail_tracking_mailgun/i18n/fr.po b/mail_tracking_mailgun/i18n/fr.po new file mode 100644 index 0000000..86d49b7 --- /dev/null +++ b/mail_tracking_mailgun/i18n/fr.po @@ -0,0 +1,24 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mail_tracking_mailgun +# +# Translators: +# dglucose , 2016 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 8.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-02 02:43+0000\n" +"PO-Revision-Date: 2016-09-02 02:43+0000\n" +"Last-Translator: dglucose , 2016\n" +"Language-Team: French (https://www.transifex.com/oca/teams/23907/fr/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: fr\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#. module: mail_tracking_mailgun +#: model:ir.model,name:mail_tracking_mailgun.model_mail_tracking_email +msgid "MailTracking email" +msgstr "Suivi du courriel: courriel" diff --git a/mail_tracking_mailgun/i18n/sl.po b/mail_tracking_mailgun/i18n/sl.po new file mode 100644 index 0000000..58d64d8 --- /dev/null +++ b/mail_tracking_mailgun/i18n/sl.po @@ -0,0 +1,24 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mail_tracking_mailgun +# +# Translators: +# Matjaž Mozetič , 2016 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 8.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-02 02:43+0000\n" +"PO-Revision-Date: 2016-09-02 02:43+0000\n" +"Last-Translator: Matjaž Mozetič , 2016\n" +"Language-Team: Slovenian (https://www.transifex.com/oca/teams/23907/sl/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: sl\n" +"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\n" + +#. module: mail_tracking_mailgun +#: model:ir.model,name:mail_tracking_mailgun.model_mail_tracking_email +msgid "MailTracking email" +msgstr "Sledenje e-pošte" From 708b72f9c06af244da14b0546cc6e293d84e9ace Mon Sep 17 00:00:00 2001 From: Antonio Espinosa Date: Fri, 15 Jul 2016 13:41:49 +0200 Subject: [PATCH 02/44] [ADD] mail_tracking_mailgun --- mail_tracking_mailgun/README.rst | 86 ++++++ mail_tracking_mailgun/__init__.py | 5 + mail_tracking_mailgun/__openerp__.py | 18 ++ mail_tracking_mailgun/models/__init__.py | 6 + .../models/ir_mail_server.py | 23 ++ .../models/mail_tracking_email.py | 198 ++++++++++++ .../static/description/icon.png | Bin 0 -> 6485 bytes .../static/description/icon.svg | 6 + mail_tracking_mailgun/tests/__init__.py | 5 + mail_tracking_mailgun/tests/test_mailgun.py | 281 ++++++++++++++++++ 10 files changed, 628 insertions(+) create mode 100644 mail_tracking_mailgun/README.rst create mode 100644 mail_tracking_mailgun/__init__.py create mode 100644 mail_tracking_mailgun/__openerp__.py create mode 100644 mail_tracking_mailgun/models/__init__.py create mode 100644 mail_tracking_mailgun/models/ir_mail_server.py create mode 100644 mail_tracking_mailgun/models/mail_tracking_email.py create mode 100644 mail_tracking_mailgun/static/description/icon.png create mode 100644 mail_tracking_mailgun/static/description/icon.svg create mode 100644 mail_tracking_mailgun/tests/__init__.py create mode 100644 mail_tracking_mailgun/tests/test_mailgun.py diff --git a/mail_tracking_mailgun/README.rst b/mail_tracking_mailgun/README.rst new file mode 100644 index 0000000..880d07e --- /dev/null +++ b/mail_tracking_mailgun/README.rst @@ -0,0 +1,86 @@ +.. 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 Mailgun +========================= + +This module integrates mail_tracking events with Mailgun webhooks. + +Mailgun (https://www.mailgun.com/) is a service that provides an e-mail +sending infrastructure through an SMTP server or via API. You can also +query that API for seeing statistics of your sent e-mails, or provide +hooks that processes the status changes in real time, which is the +function used here. + +Configuration +============= + +You must configure Mailgun webhooks in order to receive mail events: + +1. Got a Mailgun account and validate your sending domain. +2. Go to Webhook tab and configure the below URL for each event: + +.. code:: html + + https:///mail/tracking/all/ + +Replace '' with your Odoo install domain name +and '' with your database name. + +In order to validate Mailgun webhooks you have to save Mailgun api_key in +a system parameter named 'mailgun.apikey'. You can find Mailgun api_key in your +validated sending domain. + +Usage +===== + +In your mail tracking status screens (explained on module *mail_tracking*), you will +see a more accurate information, like the 'Received' or 'Bounced' status, which are +not usually detected by normal SMTP servers. + +.. 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 + +Known issues / Roadmap +====================== + +* There's no support for more than one Mailgun mail server. + +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 +------ + +* Mailgun logo: `SVG Icon `_. + +Contributors +------------ + +* 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_mailgun/__init__.py b/mail_tracking_mailgun/__init__.py new file mode 100644 index 0000000..5935294 --- /dev/null +++ b/mail_tracking_mailgun/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 Antonio Espinosa +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models diff --git a/mail_tracking_mailgun/__openerp__.py b/mail_tracking_mailgun/__openerp__.py new file mode 100644 index 0000000..e0b0d48 --- /dev/null +++ b/mail_tracking_mailgun/__openerp__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 Antonio Espinosa +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +{ + "name": "Mail tracking for Mailgun", + "summary": "Mail tracking and Mailgun webhooks integration", + "version": "8.0.1.0.0", + "category": "Social Network", + "website": "https://odoo-community.org/", + "author": "Tecnativa, " + "Odoo Community Association (OCA)", + "license": "AGPL-3", + "application": False, + "installable": True, + "depends": [ + "mail_tracking", + ], +} diff --git a/mail_tracking_mailgun/models/__init__.py b/mail_tracking_mailgun/models/__init__.py new file mode 100644 index 0000000..64dc6c2 --- /dev/null +++ b/mail_tracking_mailgun/models/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import ir_mail_server +from . import mail_tracking_email diff --git a/mail_tracking_mailgun/models/ir_mail_server.py b/mail_tracking_mailgun/models/ir_mail_server.py new file mode 100644 index 0000000..2ebb4ce --- /dev/null +++ b/mail_tracking_mailgun/models/ir_mail_server.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import json +from openerp import models + + +class IrMailServer(models.Model): + _inherit = "ir.mail_server" + + def _tracking_headers_add(self, tracking_email_id, headers): + headers = super(IrMailServer, self)._tracking_headers_add( + tracking_email_id, headers) + headers = headers or {} + metadata = { + # NOTE: We can not use 'self.env.cr.dbname' because self is + # ir.mail_server object in old API (osv.osv) + 'odoo_db': self.pool.db_name, + 'tracking_email_id': tracking_email_id, + } + headers['X-Mailgun-Variables'] = json.dumps(metadata) + return headers diff --git a/mail_tracking_mailgun/models/mail_tracking_email.py b/mail_tracking_mailgun/models/mail_tracking_email.py new file mode 100644 index 0000000..ebd7d65 --- /dev/null +++ b/mail_tracking_mailgun/models/mail_tracking_email.py @@ -0,0 +1,198 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import hashlib +import hmac +from datetime import datetime +from openerp import models, api, fields + +import logging +_logger = logging.getLogger(__name__) + + +class MailTrackingEmail(models.Model): + _inherit = "mail.tracking.email" + + def _country_search(self, country_code): + country = False + if country_code: + country = self.env['res.country'].search([ + ('code', '=', country_code.upper()), + ]) + if country: + return country.id + return False + + @property + def _mailgun_mandatory_fields(self): + return ('event', 'timestamp', 'token', 'signature', + 'tracking_email_id', 'odoo_db') + + @property + def _mailgun_event_type_mapping(self): + return { + # Mailgun event type: tracking event type + 'delivered': 'delivered', + 'opened': 'open', + 'clicked': 'click', + 'unsubscribed': 'unsub', + 'complained': 'spam', + 'bounced': 'hard_bounce', + 'dropped': 'reject', + } + + @property + def _mailgun_supported_event_types(self): + return self._mailgun_event_type_mapping.keys() + + def _mailgun_event_type_verify(self, event): + event = event or {} + mailgun_event_type = event.get('event') + if mailgun_event_type not in self._mailgun_supported_event_types: + _logger.info("Mailgun: event type '%s' not supported", + mailgun_event_type) + return False + # OK, event type is valid + return True + + def _mailgun_signature(self, api_key, timestamp, token): + return hmac.new( + key=str(api_key), + msg='{}{}'.format(str(timestamp), str(token)), + digestmod=hashlib.sha256).hexdigest() + + def _mailgun_signature_verify(self, event): + event = event or {} + api_key = self.env['ir.config_parameter'].get_param('mailgun.apikey') + if not api_key: + _logger.info("No Mailgun api key configured. " + "Please add 'mailgun.apikey' to System parameters " + "to enable Mailgun authentication webhoook requests. " + "More info at: " + "https://documentation.mailgun.com/user_manual.html" + "#webhooks") + else: + timestamp = event.get('timestamp') + token = event.get('token') + signature = event.get('signature') + event_digest = self._mailgun_signature(api_key, timestamp, token) + if signature != event_digest: + _logger.error("Mailgun: Invalid signature '%s' != '%s'", + signature, event_digest) + return False + # OK, signature is valid + return True + + def _db_verify(self, event): + event = event or {} + odoo_db = event.get('odoo_db') + current_db = self.env.cr.dbname + if odoo_db != current_db: + _logger.info("Mailgun: Database '%s' is not the current database", + odoo_db) + return False + # OK, DB is current + return True + + def _mailgun_metadata(self, mailgun_event_type, event, metadata): + # Get Mailgun timestamp when found + ts = event.get('timestamp', False) + try: + ts = float(ts) + except: + ts = False + if ts: + dt = datetime.utcfromtimestamp(ts) + metadata.update({ + 'timestamp': ts, + 'time': fields.Datetime.to_string(dt), + 'date': fields.Date.to_string(dt), + }) + # Common field mapping + mapping = { + 'recipient': 'recipient', + 'ip': 'ip', + 'user_agent': 'user-agent', + 'os_family': 'client-os', + 'ua_family': 'client-name', + 'ua_type': 'client-type', + 'url': 'url', + } + for k, v in mapping.iteritems(): + if event.get(v, False): + metadata[k] = event[v] + # Special field mapping + metadata.update({ + 'mobile': event.get('device-type') in ('mobile', 'tablet'), + 'user_country_id': self._country_search( + event.get('country', False)), + }) + # Mapping for special events + if mailgun_event_type == 'bounced': + metadata.update({ + 'error_type': event.get('code', False), + 'error_description': event.get('error', False), + 'error_details': event.get('notification', False), + }) + elif mailgun_event_type == 'dropped': + metadata.update({ + 'error_type': event.get('reason', False), + 'error_description': event.get('code', False), + 'error_details': event.get('description', False), + }) + elif mailgun_event_type == 'complained': + metadata.update({ + 'error_type': 'spam', + 'error_description': + "Recipient '%s' mark this email as spam" % + event.get('recipient', False), + }) + return metadata + + def _mailgun_tracking_get(self, event): + tracking = False + tracking_email_id = event.get('tracking_email_id', False) + if tracking_email_id and tracking_email_id.isdigit(): + tracking = self.search([('id', '=', tracking_email_id)], limit=1) + return tracking + + def _event_is_from_mailgun(self, event): + event = event or {} + return all([k in event for k in self._mailgun_mandatory_fields]) + + @api.model + def event_process(self, request, post, metadata, event_type=None): + res = super(MailTrackingEmail, self).event_process( + request, post, metadata, event_type=event_type) + if res == 'NONE' and self._event_is_from_mailgun(post): + if not self._mailgun_signature_verify(post): + res = 'ERROR: Signature' + elif not self._mailgun_event_type_verify(post): + res = 'ERROR: Event type not supported' + elif not self._db_verify(post): + res = 'ERROR: Invalid DB' + else: + res = 'OK' + if res == 'OK': + mailgun_event_type = post.get('event') + mapped_event_type = self._mailgun_event_type_mapping.get( + mailgun_event_type) or event_type + if not mapped_event_type: # pragma: no cover + res = 'ERROR: Bad event' + tracking = self._mailgun_tracking_get(post) + if not tracking: + res = 'ERROR: Tracking not found' + if res == 'OK': + # Complete metadata with mailgun event info + metadata = self._mailgun_metadata( + mailgun_event_type, post, metadata) + # Create event + tracking.event_create(mapped_event_type, metadata) + if res != 'NONE': + if event_type: + _logger.info( + "Mailgun: event '%s' process '%s'", event_type, res) + else: + _logger.info("Mailgun: event process '%s'", res) + return res diff --git a/mail_tracking_mailgun/static/description/icon.png b/mail_tracking_mailgun/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..22dc3f877c7c9046f4586b26c23404666151a034 GIT binary patch literal 6485 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 vgXkcm@}rKk$~)1CPIRIZo#;d-w#WYiZr3%g(rz + + + + + diff --git a/mail_tracking_mailgun/tests/__init__.py b/mail_tracking_mailgun/tests/__init__.py new file mode 100644 index 0000000..d7169b5 --- /dev/null +++ b/mail_tracking_mailgun/tests/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import test_mailgun diff --git a/mail_tracking_mailgun/tests/test_mailgun.py b/mail_tracking_mailgun/tests/test_mailgun.py new file mode 100644 index 0000000..f4ecf16 --- /dev/null +++ b/mail_tracking_mailgun/tests/test_mailgun.py @@ -0,0 +1,281 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp.tests.common import TransactionCase + + +class TestMailgun(TransactionCase): + def mail_send(self): + mail = self.env['mail.mail'].create({ + 'subject': 'Test subject', + 'email_from': 'from@example.com', + 'email_to': self.recipient, + 'body_html': '

This is a test message

', + }) + mail.send() + # Search tracking created + tracking_email = self.env['mail.tracking.email'].search([ + ('mail_id', '=', mail.id), + ]) + return mail, tracking_email + + def setUp(self): + super(TestMailgun, self).setUp() + self.recipient = u'to@example.com' + self.mail, self.tracking_email = self.mail_send() + self.api_key = u'key-12345678901234567890123456789012' + self.token = u'f1349299097a51b9a7d886fcb5c2735b426ba200ada6e9e149' + self.timestamp = u'1471021089' + self.signature = ('4fb6d4dbbe10ce5d620265dcd7a3c0b8' + 'ca0dede1433103891bc1ae4086e9d5b2') + self.env['ir.config_parameter'].set_param( + 'mailgun.apikey', self.api_key) + self.event = { + 'Message-Id': u'', + 'X-Mailgun-Sid': u'WyIwNjgxZSIsICJ0b0BleGFtcGxlLmNvbSIsICI3MG' + 'I0MWYiXQ==', + 'token': self.token, + 'timestamp': self.timestamp, + 'signature': self.signature, + 'domain': u'example.com', + 'message-headers': u'[]', + 'recipient': self.recipient, + 'odoo_db': self.env.cr.dbname, + 'tracking_email_id': u'%s' % self.tracking_email.id + } + self.metadata = { + 'ip': '127.0.0.1', + 'user_agent': False, + 'os_family': False, + 'ua_family': False, + } + + def event_search(self, event_type): + event = self.env['mail.tracking.event'].search([ + ('tracking_email_id', '=', self.tracking_email.id), + ('event_type', '=', event_type), + ]) + self.assertTrue(event) + return event + + def test_no_api_key(self): + self.env['ir.config_parameter'].set_param('mailgun.apikey', '') + self.test_event_delivered() + + def test_bad_signature(self): + self.event.update({ + 'event': u'delivered', + 'signature': u'bad_signature', + }) + response = self.env['mail.tracking.email'].event_process( + None, self.event, self.metadata) + self.assertEqual('ERROR: Signature', response) + + def test_bad_event_type(self): + self.event.update({ + 'event': u'bad_event', + }) + response = self.env['mail.tracking.email'].event_process( + None, self.event, self.metadata) + self.assertEqual('ERROR: Event type not supported', response) + + def test_bad_db(self): + self.event.update({ + 'event': u'delivered', + 'odoo_db': u'bad_db', + }) + response = self.env['mail.tracking.email'].event_process( + None, self.event, self.metadata) + self.assertEqual('ERROR: Invalid DB', response) + + def test_bad_ts(self): + timestamp = u'7a' # Now time will be used instead + signature = ('06cc05680f6e8110e59b41152b2d1c0f' + '1045d755ef2880ff922344325c89a6d4') + self.event.update({ + 'event': u'delivered', + 'timestamp': timestamp, + 'signature': signature, + }) + response = self.env['mail.tracking.email'].event_process( + None, self.event, self.metadata) + self.assertEqual('OK', response) + + def test_tracking_not_found(self): + self.event.update({ + 'event': u'delivered', + 'tracking_email_id': u'bad_id', + }) + response = self.env['mail.tracking.email'].event_process( + None, self.event, self.metadata) + self.assertEqual('ERROR: Tracking not found', response) + + # https://documentation.mailgun.com/user_manual.html#tracking-deliveries + def test_event_delivered(self): + self.event.update({ + 'event': u'delivered', + }) + response = self.env['mail.tracking.email'].event_process( + None, self.event, self.metadata) + self.assertEqual('OK', response) + event = self.event_search('delivered') + self.assertEqual(event.timestamp, float(self.timestamp)) + self.assertEqual(event.recipient, self.recipient) + + # https://documentation.mailgun.com/user_manual.html#tracking-opens + def test_event_opened(self): + ip = u'127.0.0.1' + user_agent = u'Odoo Test/8.0 Gecko Firefox/11.0' + os_family = u'Linux' + ua_family = u'Firefox' + ua_type = u'browser' + self.event.update({ + 'event': u'opened', + 'city': u'Mountain View', + 'country': u'US', + 'region': u'CA', + 'client-name': ua_family, + 'client-os': os_family, + 'client-type': ua_type, + 'device-type': u'desktop', + 'ip': ip, + 'user-agent': user_agent, + }) + response = self.env['mail.tracking.email'].event_process( + None, self.event, self.metadata) + self.assertEqual('OK', response) + event = self.event_search('open') + self.assertEqual(event.timestamp, float(self.timestamp)) + self.assertEqual(event.recipient, self.recipient) + self.assertEqual(event.ip, ip) + self.assertEqual(event.user_agent, user_agent) + self.assertEqual(event.os_family, os_family) + self.assertEqual(event.ua_family, ua_family) + self.assertEqual(event.ua_type, ua_type) + self.assertEqual(event.mobile, False) + self.assertEqual(event.user_country_id.code, 'US') + + # https://documentation.mailgun.com/user_manual.html#tracking-clicks + def test_event_clicked(self): + ip = u'127.0.0.1' + user_agent = u'Odoo Test/8.0 Gecko Firefox/11.0' + os_family = u'Linux' + ua_family = u'Firefox' + ua_type = u'browser' + url = u'https://odoo-community.org' + self.event.update({ + 'event': u'clicked', + 'city': u'Mountain View', + 'country': u'US', + 'region': u'CA', + 'client-name': ua_family, + 'client-os': os_family, + 'client-type': ua_type, + 'device-type': u'tablet', + 'ip': ip, + 'user-agent': user_agent, + 'url': url, + }) + response = self.env['mail.tracking.email'].event_process( + None, self.event, self.metadata, event_type='click') + self.assertEqual('OK', response) + event = self.event_search('click') + self.assertEqual(event.timestamp, float(self.timestamp)) + self.assertEqual(event.recipient, self.recipient) + self.assertEqual(event.ip, ip) + self.assertEqual(event.user_agent, user_agent) + self.assertEqual(event.os_family, os_family) + self.assertEqual(event.ua_family, ua_family) + self.assertEqual(event.ua_type, ua_type) + self.assertEqual(event.mobile, True) + self.assertEqual(event.url, url) + + # https://documentation.mailgun.com/user_manual.html#tracking-unsubscribes + def test_event_unsubscribed(self): + ip = u'127.0.0.1' + user_agent = u'Odoo Test/8.0 Gecko Firefox/11.0' + os_family = u'Linux' + ua_family = u'Firefox' + ua_type = u'browser' + self.event.update({ + 'event': u'unsubscribed', + 'city': u'Mountain View', + 'country': u'US', + 'region': u'CA', + 'client-name': ua_family, + 'client-os': os_family, + 'client-type': ua_type, + 'device-type': u'mobile', + 'ip': ip, + 'user-agent': user_agent, + }) + response = self.env['mail.tracking.email'].event_process( + None, self.event, self.metadata) + self.assertEqual('OK', response) + event = self.event_search('unsub') + self.assertEqual(event.timestamp, float(self.timestamp)) + self.assertEqual(event.recipient, self.recipient) + self.assertEqual(event.ip, ip) + self.assertEqual(event.user_agent, user_agent) + self.assertEqual(event.os_family, os_family) + self.assertEqual(event.ua_family, ua_family) + self.assertEqual(event.ua_type, ua_type) + self.assertEqual(event.mobile, True) + + # https://documentation.mailgun.com/ + # user_manual.html#tracking-spam-complaints + def test_event_complained(self): + self.event.update({ + 'event': u'complained', + }) + response = self.env['mail.tracking.email'].event_process( + None, self.event, self.metadata) + self.assertEqual('OK', response) + event = self.event_search('spam') + self.assertEqual(event.timestamp, float(self.timestamp)) + self.assertEqual(event.recipient, self.recipient) + self.assertEqual(event.error_type, 'spam') + + # https://documentation.mailgun.com/user_manual.html#tracking-bounces + def test_event_bounced(self): + code = u'550' + error = (u"5.1.1 The email account does not exist.\n" + "5.1.1 double-checking the recipient's email address") + notification = u"Please, check recipient's email address" + self.event.update({ + 'event': u'bounced', + 'code': code, + 'error': error, + 'notification': notification, + }) + response = self.env['mail.tracking.email'].event_process( + None, self.event, self.metadata) + self.assertEqual('OK', response) + event = self.event_search('hard_bounce') + self.assertEqual(event.timestamp, float(self.timestamp)) + self.assertEqual(event.recipient, self.recipient) + self.assertEqual(event.error_type, code) + self.assertEqual(event.error_description, error) + self.assertEqual(event.error_details, notification) + + # https://documentation.mailgun.com/user_manual.html#tracking-failures + def test_event_dropped(self): + reason = u'hardfail' + code = u'605' + description = u'Not delivering to previously bounced address' + self.event.update({ + 'event': u'dropped', + 'reason': reason, + 'code': code, + 'description': description, + }) + response = self.env['mail.tracking.email'].event_process( + None, self.event, self.metadata) + self.assertEqual('OK', response) + event = self.event_search('reject') + self.assertEqual(event.timestamp, float(self.timestamp)) + self.assertEqual(event.recipient, self.recipient) + self.assertEqual(event.error_type, reason) + self.assertEqual(event.error_description, code) + self.assertEqual(event.error_details, description) From b56ff3747dc015b133e242c19bec09ff40c8281d Mon Sep 17 00:00:00 2001 From: Antonio Espinosa Date: Fri, 9 Sep 2016 13:29:58 +0200 Subject: [PATCH 03/44] [8.0][IMP][mail_tracking] Speed installation time and discard concurrent events (#82) [IMP] mail_tracking: Speed installation time, discard concurrent events and other fixes --- .../models/mail_tracking_email.py | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/mail_tracking_mailgun/models/mail_tracking_email.py b/mail_tracking_mailgun/models/mail_tracking_email.py index ebd7d65..f7008ed 100644 --- a/mail_tracking_mailgun/models/mail_tracking_email.py +++ b/mail_tracking_mailgun/models/mail_tracking_email.py @@ -42,16 +42,12 @@ class MailTrackingEmail(models.Model): 'dropped': 'reject', } - @property - def _mailgun_supported_event_types(self): - return self._mailgun_event_type_mapping.keys() - def _mailgun_event_type_verify(self, event): event = event or {} mailgun_event_type = event.get('event') - if mailgun_event_type not in self._mailgun_supported_event_types: - _logger.info("Mailgun: event type '%s' not supported", - mailgun_event_type) + if mailgun_event_type not in self._mailgun_event_type_mapping: + _logger.error("Mailgun: event type '%s' not supported", + mailgun_event_type) return False # OK, event type is valid return True @@ -66,12 +62,12 @@ class MailTrackingEmail(models.Model): event = event or {} api_key = self.env['ir.config_parameter'].get_param('mailgun.apikey') if not api_key: - _logger.info("No Mailgun api key configured. " - "Please add 'mailgun.apikey' to System parameters " - "to enable Mailgun authentication webhoook requests. " - "More info at: " - "https://documentation.mailgun.com/user_manual.html" - "#webhooks") + _logger.warning("No Mailgun api key configured. " + "Please add 'mailgun.apikey' to System parameters " + "to enable Mailgun authentication webhoook " + "requests. More info at: " + "https://documentation.mailgun.com/" + "user_manual.html#webhooks") else: timestamp = event.get('timestamp') token = event.get('token') @@ -89,8 +85,8 @@ class MailTrackingEmail(models.Model): odoo_db = event.get('odoo_db') current_db = self.env.cr.dbname if odoo_db != current_db: - _logger.info("Mailgun: Database '%s' is not the current database", - odoo_db) + _logger.error("Mailgun: Database '%s' is not the current database", + odoo_db) return False # OK, DB is current return True @@ -124,7 +120,7 @@ class MailTrackingEmail(models.Model): metadata[k] = event[v] # Special field mapping metadata.update({ - 'mobile': event.get('device-type') in ('mobile', 'tablet'), + 'mobile': event.get('device-type') in {'mobile', 'tablet'}, 'user_country_id': self._country_search( event.get('country', False)), }) From 6f6231411cb3535a33f852e7f5e43ee812f6422e Mon Sep 17 00:00:00 2001 From: Antonio Espinosa Date: Tue, 13 Sep 2016 10:51:48 +0200 Subject: [PATCH 04/44] [MIG] mail_tracking_mailgun --- mail_tracking_mailgun/README.rst | 2 +- mail_tracking_mailgun/__openerp__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mail_tracking_mailgun/README.rst b/mail_tracking_mailgun/README.rst index 880d07e..9c7eb9a 100644 --- a/mail_tracking_mailgun/README.rst +++ b/mail_tracking_mailgun/README.rst @@ -42,7 +42,7 @@ not usually detected by normal SMTP servers. .. 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 + :target: https://runbot.odoo-community.org/runbot/205/9.0 Known issues / Roadmap ====================== diff --git a/mail_tracking_mailgun/__openerp__.py b/mail_tracking_mailgun/__openerp__.py index e0b0d48..3e91cb6 100644 --- a/mail_tracking_mailgun/__openerp__.py +++ b/mail_tracking_mailgun/__openerp__.py @@ -4,7 +4,7 @@ { "name": "Mail tracking for Mailgun", "summary": "Mail tracking and Mailgun webhooks integration", - "version": "8.0.1.0.0", + "version": "9.0.1.0.0", "category": "Social Network", "website": "https://odoo-community.org/", "author": "Tecnativa, " From 21e3cb5a8d875a7db9c2fa9e9a5f8c150c05e5b2 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Thu, 6 Oct 2016 16:08:38 +0200 Subject: [PATCH 05/44] [MIG] Make modules uninstallable --- mail_tracking_mailgun/__openerp__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mail_tracking_mailgun/__openerp__.py b/mail_tracking_mailgun/__openerp__.py index 3e91cb6..e607daa 100644 --- a/mail_tracking_mailgun/__openerp__.py +++ b/mail_tracking_mailgun/__openerp__.py @@ -11,7 +11,7 @@ "Odoo Community Association (OCA)", "license": "AGPL-3", "application": False, - "installable": True, + 'installable': False, "depends": [ "mail_tracking", ], From 9adb6bd763dc49856295abeca92547e6d0910156 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Thu, 6 Oct 2016 16:08:42 +0200 Subject: [PATCH 06/44] [MIG] Rename manifest files --- mail_tracking_mailgun/{__openerp__.py => __manifest__.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename mail_tracking_mailgun/{__openerp__.py => __manifest__.py} (100%) diff --git a/mail_tracking_mailgun/__openerp__.py b/mail_tracking_mailgun/__manifest__.py similarity index 100% rename from mail_tracking_mailgun/__openerp__.py rename to mail_tracking_mailgun/__manifest__.py From 24e504406599e2c65332c74c6d72cd20d802d75a Mon Sep 17 00:00:00 2001 From: Damien Bouvy Date: Fri, 14 Oct 2016 22:57:30 +0200 Subject: [PATCH 07/44] [MIG] mail_tracking_mailgun: Migrated to 10.0 --- mail_tracking_mailgun/__manifest__.py | 4 ++-- mail_tracking_mailgun/models/ir_mail_server.py | 6 ++---- mail_tracking_mailgun/models/mail_tracking_email.py | 2 +- mail_tracking_mailgun/tests/test_mailgun.py | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/mail_tracking_mailgun/__manifest__.py b/mail_tracking_mailgun/__manifest__.py index e607daa..51e92ce 100644 --- a/mail_tracking_mailgun/__manifest__.py +++ b/mail_tracking_mailgun/__manifest__.py @@ -4,14 +4,14 @@ { "name": "Mail tracking for Mailgun", "summary": "Mail tracking and Mailgun webhooks integration", - "version": "9.0.1.0.0", + "version": "10.0.1.0.0", "category": "Social Network", "website": "https://odoo-community.org/", "author": "Tecnativa, " "Odoo Community Association (OCA)", "license": "AGPL-3", "application": False, - 'installable': False, + 'installable': True, "depends": [ "mail_tracking", ], diff --git a/mail_tracking_mailgun/models/ir_mail_server.py b/mail_tracking_mailgun/models/ir_mail_server.py index 2ebb4ce..0fe8906 100644 --- a/mail_tracking_mailgun/models/ir_mail_server.py +++ b/mail_tracking_mailgun/models/ir_mail_server.py @@ -3,7 +3,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import json -from openerp import models +from odoo import models class IrMailServer(models.Model): @@ -14,9 +14,7 @@ class IrMailServer(models.Model): tracking_email_id, headers) headers = headers or {} metadata = { - # NOTE: We can not use 'self.env.cr.dbname' because self is - # ir.mail_server object in old API (osv.osv) - 'odoo_db': self.pool.db_name, + 'odoo_db': self.env.cr.dbname, 'tracking_email_id': tracking_email_id, } headers['X-Mailgun-Variables'] = json.dumps(metadata) diff --git a/mail_tracking_mailgun/models/mail_tracking_email.py b/mail_tracking_mailgun/models/mail_tracking_email.py index f7008ed..69a96ad 100644 --- a/mail_tracking_mailgun/models/mail_tracking_email.py +++ b/mail_tracking_mailgun/models/mail_tracking_email.py @@ -5,7 +5,7 @@ import hashlib import hmac from datetime import datetime -from openerp import models, api, fields +from odoo import models, api, fields import logging _logger = logging.getLogger(__name__) diff --git a/mail_tracking_mailgun/tests/test_mailgun.py b/mail_tracking_mailgun/tests/test_mailgun.py index f4ecf16..f00a067 100644 --- a/mail_tracking_mailgun/tests/test_mailgun.py +++ b/mail_tracking_mailgun/tests/test_mailgun.py @@ -2,7 +2,7 @@ # Copyright 2016 Antonio Espinosa - # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from openerp.tests.common import TransactionCase +from odoo.tests.common import TransactionCase class TestMailgun(TransactionCase): From b2ecc17d7bf6251f174206dcfedd13f8ea886017 Mon Sep 17 00:00:00 2001 From: OCA Transbot Date: Tue, 29 Nov 2016 12:35:16 -0500 Subject: [PATCH 08/44] OCA Transbot updated translations from Transifex --- mail_tracking_mailgun/i18n/hr.po | 29 +++++++++++++++++++++++++++++ mail_tracking_mailgun/i18n/it.po | 29 +++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 mail_tracking_mailgun/i18n/hr.po create mode 100644 mail_tracking_mailgun/i18n/it.po diff --git a/mail_tracking_mailgun/i18n/hr.po b/mail_tracking_mailgun/i18n/hr.po new file mode 100644 index 0000000..ca05127 --- /dev/null +++ b/mail_tracking_mailgun/i18n/hr.po @@ -0,0 +1,29 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mail_tracking_mailgun +# +# Translators: +# OCA Transbot , 2016 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-11-29 14:24+0000\n" +"PO-Revision-Date: 2016-11-29 14:24+0000\n" +"Last-Translator: OCA Transbot , 2016\n" +"Language-Team: Croatian (https://www.transifex.com/oca/teams/23907/hr/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: hr\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" + +#. module: mail_tracking_mailgun +#: model:ir.model,name:mail_tracking_mailgun.model_mail_tracking_email +msgid "MailTracking email" +msgstr "" + +#. module: mail_tracking_mailgun +#: model:ir.model,name:mail_tracking_mailgun.model_ir_mail_server +msgid "ir.mail_server" +msgstr "ir.mail_server" diff --git a/mail_tracking_mailgun/i18n/it.po b/mail_tracking_mailgun/i18n/it.po new file mode 100644 index 0000000..11449b0 --- /dev/null +++ b/mail_tracking_mailgun/i18n/it.po @@ -0,0 +1,29 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mail_tracking_mailgun +# +# Translators: +# OCA Transbot , 2016 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-11-29 14:24+0000\n" +"PO-Revision-Date: 2016-11-29 14:24+0000\n" +"Last-Translator: OCA Transbot , 2016\n" +"Language-Team: Italian (https://www.transifex.com/oca/teams/23907/it/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: it\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: mail_tracking_mailgun +#: model:ir.model,name:mail_tracking_mailgun.model_mail_tracking_email +msgid "MailTracking email" +msgstr "" + +#. module: mail_tracking_mailgun +#: model:ir.model,name:mail_tracking_mailgun.model_ir_mail_server +msgid "ir.mail_server" +msgstr "ir.mail_server" From 003d7d89e44211a3fa26c02b279ef95825ae7a84 Mon Sep 17 00:00:00 2001 From: Rafael Blasco Date: Tue, 14 Mar 2017 12:24:47 +0100 Subject: [PATCH 09/44] Add Contributors Both Carlos and me have work a lot in this module, maybe not coding but much testing and definition in usability --- mail_tracking_mailgun/README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mail_tracking_mailgun/README.rst b/mail_tracking_mailgun/README.rst index 9c7eb9a..dc9f928 100644 --- a/mail_tracking_mailgun/README.rst +++ b/mail_tracking_mailgun/README.rst @@ -69,6 +69,8 @@ Contributors ------------ * Antonio Espinosa +* Carlos Dauden +* Rafael Blasco Maintainer ---------- From a757066b7a93217cc5b7ace518cfe1930bf2f6d1 Mon Sep 17 00:00:00 2001 From: OCA Transbot Date: Sat, 20 May 2017 10:34:05 +0200 Subject: [PATCH 10/44] OCA Transbot updated translations from Transifex --- mail_tracking_mailgun/i18n/it.po | 9 +++++---- mail_tracking_mailgun/i18n/nl_NL.po | 29 +++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 mail_tracking_mailgun/i18n/nl_NL.po diff --git a/mail_tracking_mailgun/i18n/it.po b/mail_tracking_mailgun/i18n/it.po index 11449b0..f68125f 100644 --- a/mail_tracking_mailgun/i18n/it.po +++ b/mail_tracking_mailgun/i18n/it.po @@ -4,13 +4,14 @@ # # Translators: # OCA Transbot , 2016 +# Marius Marolla , 2017 msgid "" msgstr "" "Project-Id-Version: Odoo Server 10.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-11-29 14:24+0000\n" -"PO-Revision-Date: 2016-11-29 14:24+0000\n" -"Last-Translator: OCA Transbot , 2016\n" +"POT-Creation-Date: 2017-05-17 01:02+0000\n" +"PO-Revision-Date: 2017-05-17 01:02+0000\n" +"Last-Translator: Marius Marolla , 2017\n" "Language-Team: Italian (https://www.transifex.com/oca/teams/23907/it/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -21,7 +22,7 @@ msgstr "" #. module: mail_tracking_mailgun #: model:ir.model,name:mail_tracking_mailgun.model_mail_tracking_email msgid "MailTracking email" -msgstr "" +msgstr "Messaggio Tracciamento Email" #. module: mail_tracking_mailgun #: model:ir.model,name:mail_tracking_mailgun.model_ir_mail_server diff --git a/mail_tracking_mailgun/i18n/nl_NL.po b/mail_tracking_mailgun/i18n/nl_NL.po new file mode 100644 index 0000000..9ffa419 --- /dev/null +++ b/mail_tracking_mailgun/i18n/nl_NL.po @@ -0,0 +1,29 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mail_tracking_mailgun +# +# Translators: +# Peter Hageman , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-05-20 08:36+0000\n" +"PO-Revision-Date: 2017-05-20 08:36+0000\n" +"Last-Translator: Peter Hageman , 2017\n" +"Language-Team: Dutch (Netherlands) (https://www.transifex.com/oca/teams/23907/nl_NL/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: nl_NL\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: mail_tracking_mailgun +#: model:ir.model,name:mail_tracking_mailgun.model_mail_tracking_email +msgid "MailTracking email" +msgstr "" + +#. module: mail_tracking_mailgun +#: model:ir.model,name:mail_tracking_mailgun.model_ir_mail_server +msgid "ir.mail_server" +msgstr "ir.mail_server" From ed9b469533fbed0e224c167b9a9de801dec8cfb7 Mon Sep 17 00:00:00 2001 From: David Vidal Date: Fri, 14 Jul 2017 19:54:29 +0200 Subject: [PATCH 11/44] [IMP] mute logger on tests error responses so Travis doesn't fail --- mail_tracking_mailgun/tests/test_mailgun.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/mail_tracking_mailgun/tests/test_mailgun.py b/mail_tracking_mailgun/tests/test_mailgun.py index f00a067..5aa6b3b 100644 --- a/mail_tracking_mailgun/tests/test_mailgun.py +++ b/mail_tracking_mailgun/tests/test_mailgun.py @@ -2,6 +2,7 @@ # Copyright 2016 Antonio Espinosa - # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo.tools import mute_logger from odoo.tests.common import TransactionCase @@ -63,6 +64,8 @@ class TestMailgun(TransactionCase): self.env['ir.config_parameter'].set_param('mailgun.apikey', '') self.test_event_delivered() + @mute_logger('odoo.addons.mail_tracking_mailgun.models' + '.mail_tracking_email') def test_bad_signature(self): self.event.update({ 'event': u'delivered', @@ -72,6 +75,8 @@ class TestMailgun(TransactionCase): None, self.event, self.metadata) self.assertEqual('ERROR: Signature', response) + @mute_logger('odoo.addons.mail_tracking_mailgun.models' + '.mail_tracking_email') def test_bad_event_type(self): self.event.update({ 'event': u'bad_event', @@ -80,6 +85,8 @@ class TestMailgun(TransactionCase): None, self.event, self.metadata) self.assertEqual('ERROR: Event type not supported', response) + @mute_logger('odoo.addons.mail_tracking_mailgun.models' + '.mail_tracking_email') def test_bad_db(self): self.event.update({ 'event': u'delivered', @@ -102,6 +109,8 @@ class TestMailgun(TransactionCase): None, self.event, self.metadata) self.assertEqual('OK', response) + @mute_logger('odoo.addons.mail_tracking_mailgun.models' + '.mail_tracking_email') def test_tracking_not_found(self): self.event.update({ 'event': u'delivered', From 3e8273581bb4dd48d71460007a17edc0ae5750cb Mon Sep 17 00:00:00 2001 From: OCA Transbot Date: Sat, 22 Jul 2017 11:35:52 +0200 Subject: [PATCH 12/44] OCA Transbot updated translations from Transifex --- mail_tracking_mailgun/i18n/ca.po | 29 +++++++++++++++++++++++++++++ mail_tracking_mailgun/i18n/de.po | 29 +++++++++++++++++++++++++++++ mail_tracking_mailgun/i18n/fi.po | 29 +++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 mail_tracking_mailgun/i18n/ca.po create mode 100644 mail_tracking_mailgun/i18n/de.po create mode 100644 mail_tracking_mailgun/i18n/fi.po diff --git a/mail_tracking_mailgun/i18n/ca.po b/mail_tracking_mailgun/i18n/ca.po new file mode 100644 index 0000000..e83b1d7 --- /dev/null +++ b/mail_tracking_mailgun/i18n/ca.po @@ -0,0 +1,29 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mail_tracking_mailgun +# +# Translators: +# OCA Transbot , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-07-22 00:51+0000\n" +"PO-Revision-Date: 2017-07-22 00:51+0000\n" +"Last-Translator: OCA Transbot , 2017\n" +"Language-Team: Catalan (https://www.transifex.com/oca/teams/23907/ca/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: ca\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: mail_tracking_mailgun +#: model:ir.model,name:mail_tracking_mailgun.model_mail_tracking_email +msgid "MailTracking email" +msgstr "MailTracking correu electrònic" + +#. module: mail_tracking_mailgun +#: model:ir.model,name:mail_tracking_mailgun.model_ir_mail_server +msgid "ir.mail_server" +msgstr "" diff --git a/mail_tracking_mailgun/i18n/de.po b/mail_tracking_mailgun/i18n/de.po new file mode 100644 index 0000000..460bd18 --- /dev/null +++ b/mail_tracking_mailgun/i18n/de.po @@ -0,0 +1,29 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mail_tracking_mailgun +# +# Translators: +# OCA Transbot , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-07-22 00:51+0000\n" +"PO-Revision-Date: 2017-07-22 00:51+0000\n" +"Last-Translator: OCA Transbot , 2017\n" +"Language-Team: German (https://www.transifex.com/oca/teams/23907/de/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: de\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: mail_tracking_mailgun +#: model:ir.model,name:mail_tracking_mailgun.model_mail_tracking_email +msgid "MailTracking email" +msgstr "Mailverfolgung" + +#. module: mail_tracking_mailgun +#: model:ir.model,name:mail_tracking_mailgun.model_ir_mail_server +msgid "ir.mail_server" +msgstr "" diff --git a/mail_tracking_mailgun/i18n/fi.po b/mail_tracking_mailgun/i18n/fi.po new file mode 100644 index 0000000..15e2fad --- /dev/null +++ b/mail_tracking_mailgun/i18n/fi.po @@ -0,0 +1,29 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mail_tracking_mailgun +# +# Translators: +# OCA Transbot , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-07-22 00:51+0000\n" +"PO-Revision-Date: 2017-07-22 00:51+0000\n" +"Last-Translator: OCA Transbot , 2017\n" +"Language-Team: Finnish (https://www.transifex.com/oca/teams/23907/fi/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: fi\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: mail_tracking_mailgun +#: model:ir.model,name:mail_tracking_mailgun.model_mail_tracking_email +msgid "MailTracking email" +msgstr "MailTracking sähköposti" + +#. module: mail_tracking_mailgun +#: model:ir.model,name:mail_tracking_mailgun.model_ir_mail_server +msgid "ir.mail_server" +msgstr "" From 3ca3ee4e49dbe1ae087b541f2a3f64a6aec0134a Mon Sep 17 00:00:00 2001 From: David Date: Tue, 17 Oct 2017 20:38:11 +0200 Subject: [PATCH 13/44] [10.0][IMP] mail_tracking_mailgun: add partner mail checks --- mail_tracking_mailgun/README.rst | 26 +++- mail_tracking_mailgun/__init__.py | 1 - mail_tracking_mailgun/__manifest__.py | 11 +- mail_tracking_mailgun/i18n/es.po | 133 ++++++++++++++++- mail_tracking_mailgun/models/__init__.py | 3 +- .../models/ir_mail_server.py | 2 +- .../models/mail_tracking_email.py | 58 +++++++- .../models/mail_tracking_event.py | 21 +++ mail_tracking_mailgun/models/res_partner.py | 135 ++++++++++++++++++ mail_tracking_mailgun/tests/__init__.py | 1 - mail_tracking_mailgun/tests/test_mailgun.py | 131 ++++++++++++++++- .../views/mail_tracking_email.xml | 16 +++ mail_tracking_mailgun/views/res_partner.xml | 28 ++++ 13 files changed, 548 insertions(+), 18 deletions(-) create mode 100644 mail_tracking_mailgun/models/mail_tracking_event.py create mode 100644 mail_tracking_mailgun/models/res_partner.py create mode 100644 mail_tracking_mailgun/views/mail_tracking_email.xml create mode 100644 mail_tracking_mailgun/views/res_partner.xml diff --git a/mail_tracking_mailgun/README.rst b/mail_tracking_mailgun/README.rst index dc9f928..7f377ad 100644 --- a/mail_tracking_mailgun/README.rst +++ b/mail_tracking_mailgun/README.rst @@ -29,9 +29,16 @@ You must configure Mailgun webhooks in order to receive mail events: Replace '' with your Odoo install domain name and '' with your database name. -In order to validate Mailgun webhooks you have to save Mailgun api_key in -a system parameter named 'mailgun.apikey'. You can find Mailgun api_key in your -validated sending domain. +In order to validate Mailgun webhooks you have to configure the following system +parameters: + +- `mailgun.apikey`: You can find Mailgun api_key in your validated sending + domain. +- `mailgun.api_url`: It should be fine as it is, but it could change in the + future. +- `mailgun.validation_key`: If you want to be able to check mail address + validity you must config this parameter with your account Public Validation + Key. Usage ===== @@ -40,6 +47,17 @@ In your mail tracking status screens (explained on module *mail_tracking*), you see a more accurate information, like the 'Received' or 'Bounced' status, which are not usually detected by normal SMTP servers. +It's also possible to make some checks to the partner's email addresses against the Mailgun API: + +- Check if the partner's email is in Mailgun's bounced list. +- Check the validity of the partner's mailbox. +- Force the partner's email into Mailgun's bounced list or delete from it. + +It's also possible to manually check a message mailgun tracking when the webhook +couldn't be captured. For that, go to that message tracking form, press the +button *Check Mailgun*. It's important to note that tracking events have quite a +short lifespan, so after 24h they won't be recoverable. + .. 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/9.0 @@ -70,6 +88,8 @@ Contributors * Antonio Espinosa * Carlos Dauden +* Pedro M. Baeza +* David Vidal * Rafael Blasco Maintainer diff --git a/mail_tracking_mailgun/__init__.py b/mail_tracking_mailgun/__init__.py index 5935294..ec50cfc 100644 --- a/mail_tracking_mailgun/__init__.py +++ b/mail_tracking_mailgun/__init__.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# Copyright 2016 Antonio Espinosa # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import models diff --git a/mail_tracking_mailgun/__manifest__.py b/mail_tracking_mailgun/__manifest__.py index 51e92ce..9fe931e 100644 --- a/mail_tracking_mailgun/__manifest__.py +++ b/mail_tracking_mailgun/__manifest__.py @@ -1,10 +1,13 @@ # -*- coding: utf-8 -*- -# Copyright 2016 Antonio Espinosa +# Copyright 2016 Tecnativa - Antonio Espinosa +# Copyright 2016 Tecnativa - Carlos Dauden +# Copyright 2017 Tecnativa - Pedro M. Baeza +# Copyright 2017 Tecnativa - David Vidal # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { "name": "Mail tracking for Mailgun", "summary": "Mail tracking and Mailgun webhooks integration", - "version": "10.0.1.0.0", + "version": "10.0.1.1.0", "category": "Social Network", "website": "https://odoo-community.org/", "author": "Tecnativa, " @@ -15,4 +18,8 @@ "depends": [ "mail_tracking", ], + "data": [ + "views/res_partner.xml", + "views/mail_tracking_email.xml", + ] } diff --git a/mail_tracking_mailgun/i18n/es.po b/mail_tracking_mailgun/i18n/es.po index 9b6e844..e846022 100644 --- a/mail_tracking_mailgun/i18n/es.po +++ b/mail_tracking_mailgun/i18n/es.po @@ -8,17 +8,138 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 8.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-09-02 02:43+0000\n" -"PO-Revision-Date: 2016-09-02 02:43+0000\n" -"Last-Translator: OCA Transbot , 2016\n" -"Language-Team: Spanish (https://www.transifex.com/oca/teams/23907/es/)\n" +"POT-Creation-Date: 2017-11-22 09:11+0000\n" +"PO-Revision-Date: 2017-11-22 09:11+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" -"Language: es\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: \n" + +#. module: mail_tracking_mailgun +#: code:addons/mail_tracking_mailgun/models/res_partner.py:82 +#, python-format +msgid "%s couldn't be verified. Either the request couln't be completed or the mailbox provider doesn't support email verification" +msgstr "%s no ha podido ser verificado. Puede que la petición no se haya completado o que el buzón no soporta validación de correo electrónico" + +#. module: mail_tracking_mailgun +#: code:addons/mail_tracking_mailgun/models/res_partner.py:76 +#, python-format +msgid "%s failed the mailbox verification. Please check it in order to avoid sending issues" +msgstr "%s no ha pasado la validación de buzón. Compruébela para prevenir problemas con los envíos" + +#. module: mail_tracking_mailgun +#: code:addons/mail_tracking_mailgun/models/res_partner.py:69 +#, python-format +msgid "%s is not a valid email address. Please check it in order to avoid sending issues" +msgstr "%s no es una dirección válida de correo electrónico. Compruébela para prevenir problemas con los envíos" + +#. module: mail_tracking_mailgun +#: code:addons/mail_tracking_mailgun/models/mail_tracking_email.py:75 +#, python-format +msgid "A Mailgun domain value is needed!" +msgstr "¡Se necesita un valor de dominio para Mailgun!\"" + +#. module: mail_tracking_mailgun +#: model:ir.ui.view,arch_db:mail_tracking_mailgun.view_partner_form_mailgun +msgid "Check Mailgun" +msgstr "Comprobar Mailgun" + +#. module: mail_tracking_mailgun +#: model:ir.ui.view,arch_db:mail_tracking_mailgun.view_partner_form_mailgun +msgid "Check email validity" +msgstr "Comprobar validez de email" + +#. module: mail_tracking_mailgun +#: code:addons/mail_tracking_mailgun/models/mail_tracking_email.py:236 +#, python-format +msgid "Couldn't retrieve Mailgun information" +msgstr "No se ha podido obtener información desde Mailgun" + +#. module: mail_tracking_mailgun +#: code:addons/mail_tracking_mailgun/models/res_partner.py:30 +#, python-format +msgid "" +"Email has been bounced: %s\n" +"Reason: %s\n" +"Event: %s" +msgstr "" +"El correo ha sido rebotado: %s\n" +"Razón: %s\n" +"Evento: %s" + +#. module: mail_tracking_mailgun +#: code:addons/mail_tracking_mailgun/models/res_partner.py:56 +#, python-format +msgid "Error %s trying to check mailof connection" +msgstr "Error %s al intentar comprobar el correo electrónicodeconexión" + +#. module: mail_tracking_mailgun +#: code:addons/mail_tracking_mailgun/models/mail_tracking_email.py:240 +#, python-format +msgid "Event information not longer stored" +msgstr "La información del evento ha caducado" #. module: mail_tracking_mailgun #: model:ir.model,name:mail_tracking_mailgun.model_mail_tracking_email msgid "MailTracking email" msgstr "MailTracking email" + +#. module: mail_tracking_mailgun +#: model:ir.model,name:mail_tracking_mailgun.model_mail_tracking_event +msgid "MailTracking event" +msgstr "MailTracking event" + +#. module: mail_tracking_mailgun +#: model:ir.ui.view,arch_db:mail_tracking_mailgun.view_partner_form_mailgun +msgid "Mailgun" +msgstr "Mailgun" + +#. module: mail_tracking_mailgun +#: code:addons/mail_tracking_mailgun/models/res_partner.py:62 +#, python-format +msgid "Mailgun Error. Mailbox verification value wasn't returned" +msgstr "Error de Mailgun. No se ha devuelto el valor de verificación de buzón" + +#. module: mail_tracking_mailgun +#: model:ir.model.fields,field_description:mail_tracking_mailgun.field_mail_tracking_event_mailgun_id +msgid "Mailgun Event ID" +msgstr "Mailgun Event ID" + +#. module: mail_tracking_mailgun +#: model:ir.model,name:mail_tracking_mailgun.model_res_partner +msgid "Partner" +msgstr "Empresa" + +#. module: mail_tracking_mailgun +#: model:ir.ui.view,arch_db:mail_tracking_mailgun.mailgun_manual_check +msgid "Re-sync Mailgun" +msgstr "Resincronizar Mailgun" + +#. module: mail_tracking_mailgun +#: model:ir.ui.view,arch_db:mail_tracking_mailgun.view_partner_form_mailgun +msgid "Set Bounced" +msgstr "Marcar como rebotado" + +#. module: mail_tracking_mailgun +#: code:addons/mail_tracking_mailgun/models/mail_tracking_email.py:70 +#, python-format +msgid "There is no Mailgun API key!" +msgstr "¡No hay clave de API de Mailgun!" + +#. module: mail_tracking_mailgun +#: model:ir.ui.view,arch_db:mail_tracking_mailgun.view_partner_form_mailgun +msgid "Unset Bounced" +msgstr "Desmarcar como rebotado" + +#. module: mail_tracking_mailgun +#: code:addons/mail_tracking_mailgun/models/res_partner.py:46 +#, python-format +msgid "You need to configure mailgun.validation_key in order to be able to check mails validity" +msgstr "Necesitas configurar mailgun.validation_key para poder comprobar la validez de direcciones de correo" + +#. module: mail_tracking_mailgun +#: model:ir.model,name:mail_tracking_mailgun.model_ir_mail_server +msgid "ir.mail_server" +msgstr "ir.mail_server" diff --git a/mail_tracking_mailgun/models/__init__.py b/mail_tracking_mailgun/models/__init__.py index 64dc6c2..b0449e5 100644 --- a/mail_tracking_mailgun/models/__init__.py +++ b/mail_tracking_mailgun/models/__init__.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- -# Copyright 2016 Antonio Espinosa - # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import ir_mail_server from . import mail_tracking_email +from . import mail_tracking_event +from . import res_partner diff --git a/mail_tracking_mailgun/models/ir_mail_server.py b/mail_tracking_mailgun/models/ir_mail_server.py index 0fe8906..055c8c1 100644 --- a/mail_tracking_mailgun/models/ir_mail_server.py +++ b/mail_tracking_mailgun/models/ir_mail_server.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2016 Antonio Espinosa - +# Copyright 2016 Tecnativa - Antonio Espinosa # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import json diff --git a/mail_tracking_mailgun/models/mail_tracking_email.py b/mail_tracking_mailgun/models/mail_tracking_email.py index 69a96ad..2364762 100644 --- a/mail_tracking_mailgun/models/mail_tracking_email.py +++ b/mail_tracking_mailgun/models/mail_tracking_email.py @@ -1,11 +1,15 @@ # -*- coding: utf-8 -*- -# Copyright 2016 Antonio Espinosa - +# Copyright 2016 Tecnativa - Antonio Espinosa +# Copyright 2017 Tecnativa - David Vidal # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import hashlib import hmac +import json +import requests from datetime import datetime -from odoo import models, api, fields +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError import logging _logger = logging.getLogger(__name__) @@ -40,6 +44,7 @@ class MailTrackingEmail(models.Model): 'complained': 'spam', 'bounced': 'hard_bounce', 'dropped': 'reject', + 'accepted': 'sent', } def _mailgun_event_type_verify(self, event): @@ -58,6 +63,19 @@ class MailTrackingEmail(models.Model): msg='{}{}'.format(str(timestamp), str(token)), digestmod=hashlib.sha256).hexdigest() + def _mailgun_values(self): + icp = self.env['ir.config_parameter'] + api_key = icp.get_param('mailgun.apikey') + if not api_key: + raise ValidationError(_('There is no Mailgun API key!')) + api_url = icp.get_param( + 'mailgun.api_url', 'https://api.mailgun.net/v3') + domain = icp.get_param('mail.catchall.domain') + if not domain: + raise ValidationError(_('A Mailgun domain value is needed!')) + validation_key = icp.get_param('mailgun.validation_key') + return api_key, api_url, domain, validation_key + def _mailgun_signature_verify(self, event): event = event or {} api_key = self.env['ir.config_parameter'].get_param('mailgun.apikey') @@ -104,6 +122,7 @@ class MailTrackingEmail(models.Model): 'timestamp': ts, 'time': fields.Datetime.to_string(dt), 'date': fields.Date.to_string(dt), + 'mailgun_id': event.get('id', False) }) # Common field mapping mapping = { @@ -192,3 +211,38 @@ class MailTrackingEmail(models.Model): else: _logger.info("Mailgun: event process '%s'", res) return res + + @api.multi + def action_manual_check_mailgun(self): + """ + Manual check against Mailgun API + API Documentation: + https://documentation.mailgun.com/en/latest/api-events.html + """ + api_key, api_url, domain, validation_key = self._mailgun_values() + for tracking in self: + message_id = tracking.mail_message_id.message_id.replace( + "<", "").replace(">", "") + res = requests.get( + '%s/%s/events' % (api_url, domain), + auth=("api", api_key), + params={ + "begin": tracking.timestamp, + "ascending": "yes", + "message-id": message_id, + } + ) + if not res or res.status_code != 200: + raise ValidationError(_( + "Couldn't retrieve Mailgun information")) + content = json.loads(res.content, res.apparent_encoding) + if "items" not in content: + raise ValidationError(_("Event information not longer stored")) + for item in content["items"]: + if not self.env['mail.tracking.event'].search( + [('mailgun_id', '=', item["id"])]): + mapped_event_type = self._mailgun_event_type_mapping.get( + item["event"]) or False + metadata = self._mailgun_metadata( + mapped_event_type, item, {}) + tracking.event_create(mapped_event_type, metadata) diff --git a/mail_tracking_mailgun/models/mail_tracking_event.py b/mail_tracking_mailgun/models/mail_tracking_event.py new file mode 100644 index 0000000..de3fe3e --- /dev/null +++ b/mail_tracking_mailgun/models/mail_tracking_event.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Tecnativa - David Vidal +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import models, fields + + +class MailTrackingEvent(models.Model): + _inherit = "mail.tracking.event" + + mailgun_id = fields.Char( + string="Mailgun Event ID", + copy="False", + readonly=True, + ) + + def _process_data(self, tracking_email, metadata, event_type, state): + res = super(MailTrackingEvent, self)._process_data( + tracking_email, metadata, event_type, state) + res.update({'mailgun_id': metadata.get('mailgun_id', False)}) + return res diff --git a/mail_tracking_mailgun/models/res_partner.py b/mail_tracking_mailgun/models/res_partner.py new file mode 100644 index 0000000..9657647 --- /dev/null +++ b/mail_tracking_mailgun/models/res_partner.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 Tecnativa - Antonio Espinosa +# Copyright 2016 Tecnativa - Carlos Dauden +# Copyright 2017 Tecnativa - Pedro M. Baeza +# Copyright 2017 Tecnativa - David Vidal +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import requests +import json + +from openerp import _, api, models +from openerp.exceptions import UserError + + +class ResPartner(models.Model): + _inherit = 'res.partner' + + @api.multi + def email_bounced_set(self, tracking_emails, reason, event=None): + res = super(ResPartner, self).email_bounced_set( + tracking_emails, reason, event=event) + self._email_bounced_set(reason, event) + return res + + @api.multi + def _email_bounced_set(self, reason, event): + for partner in self: + if not partner.email: + continue + body = _('Email has been bounced: %s\n' + 'Reason: %s\n' + 'Event: %s') % (partner.email, reason, + event['Message-Id'] or '') + partner.message_post(body=body) + + @api.multi + def check_email_validity(self): + """ + Checks mailbox validity with Mailgun's API + API documentation: + https://documentation.mailgun.com/en/latest/api-email-validation.html + """ + api_key, api_url, domain, validation_key = self.env[ + 'mail.tracking.email']._mailgun_values() + if not validation_key: + raise UserError(_('You need to configure mailgun.validation_key' + ' in order to be able to check mails validity')) + for partner in self: + res = requests.get( + "%s/address/validate" % api_url, + auth=("api", validation_key), params={ + "address": partner.email, + "mailbox_verification": True, + }) + if not res or res.status_code != 200: + raise UserError(_( + 'Error %s trying to ' + 'check mail' % res.status_code or 'of connection')) + content = json.loads(res.content, res.apparent_encoding) + if 'mailbox_verification' not in content: + raise UserError( + _("Mailgun Error. Mailbox verification value wasn't" + " returned")) + # Not a valid address: API sets 'is_valid' as False + # and 'mailbox_verification' as None + if not content['is_valid']: + partner.email_bounced = True + raise UserError( + _('%s is not a valid email address. Please check it ' + 'in order to avoid sending issues') % (partner.email)) + # If the mailbox is not valid API returns 'mailbox_verification' + # as a string with value 'false' + if content['mailbox_verification'] == 'false': + partner.email_bounced = True + raise UserError( + _('%s failed the mailbox verification. Please check it ' + 'in order to avoid sending issues') % (partner.email)) + # If Mailgun can't complete the validation request the API returns + # 'mailbox_verification' as a string set to 'unknown' + if content['mailbox_verification'] == 'unknown': + raise UserError( + _("%s couldn't be verified. Either the request couln't be " + "completed or the mailbox provider doesn't support " + "email verification") % (partner.email)) + + @api.multi + def check_email_bounced(self): + """ + Checks if the partner's email is in Mailgun's bounces list + API documentation: + https://documentation.mailgun.com/en/latest/api-suppressions.html + """ + api_key, api_url, domain, validation_key = self.env[ + 'mail.tracking.email']._mailgun_values() + for partner in self: + res = requests.get( + '%s/%s/bounces/%s' % (api_url, domain, partner.email), + auth=("api", api_key)) + if res.status_code == 200 and not partner.email_bounced: + partner.email_bounced = True + elif res.status_code == 404 and partner.email_bounced: + partner.email_bounced = False + + @api.multi + def force_set_bounced(self): + """ + Forces partner's email into Mailgun's bounces list + API documentation: + https://documentation.mailgun.com/en/latest/api-suppressions.html + """ + api_key, api_url, domain, validation_key = self.env[ + 'mail.tracking.email']._mailgun_values() + for partner in self: + res = requests.post( + '%s/%s/bounces' % (api_url, domain), + auth=("api", api_key), + data={'address': partner.email}) + partner.email_bounced = ( + res.status_code == 200 and not partner.email_bounced) + + @api.multi + def force_unset_bounced(self): + """ + Forces partner's email deletion from Mailgun's bounces list + API documentation: + https://documentation.mailgun.com/en/latest/api-suppressions.html + """ + api_key, api_url, domain, validation_key = self.env[ + 'mail.tracking.email']._mailgun_values() + for partner in self: + res = requests.delete( + '%s/%s/bounces/%s' % (api_url, domain, partner.email), + auth=("api", api_key)) + if res.status_code in (200, 404) and partner.email_bounced: + partner.email_bounced = False diff --git a/mail_tracking_mailgun/tests/__init__.py b/mail_tracking_mailgun/tests/__init__.py index d7169b5..bfbcec7 100644 --- a/mail_tracking_mailgun/tests/__init__.py +++ b/mail_tracking_mailgun/tests/__init__.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# Copyright 2016 Antonio Espinosa - # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import test_mailgun diff --git a/mail_tracking_mailgun/tests/test_mailgun.py b/mail_tracking_mailgun/tests/test_mailgun.py index 5aa6b3b..4180a8d 100644 --- a/mail_tracking_mailgun/tests/test_mailgun.py +++ b/mail_tracking_mailgun/tests/test_mailgun.py @@ -1,9 +1,15 @@ # -*- coding: utf-8 -*- -# Copyright 2016 Antonio Espinosa - +# Copyright 2016 Tecnativa - Antonio Espinosa +# Copyright 2017 Tecnativa - David Vidal # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo.tools import mute_logger from odoo.tests.common import TransactionCase +from odoo.exceptions import UserError, ValidationError +import mock +import json + +_packagepath = 'odoo.addons.mail_tracking_mailgun' class TestMailgun(TransactionCase): @@ -26,12 +32,17 @@ class TestMailgun(TransactionCase): self.recipient = u'to@example.com' self.mail, self.tracking_email = self.mail_send() self.api_key = u'key-12345678901234567890123456789012' + self.domain = u'example.com' self.token = u'f1349299097a51b9a7d886fcb5c2735b426ba200ada6e9e149' self.timestamp = u'1471021089' self.signature = ('4fb6d4dbbe10ce5d620265dcd7a3c0b8' 'ca0dede1433103891bc1ae4086e9d5b2') self.env['ir.config_parameter'].set_param( 'mailgun.apikey', self.api_key) + self.env['ir.config_parameter'].set_param( + 'mail.catchall.domain', self.domain) + self.env['ir.config_parameter'].set_param( + 'mailgun.validation_key', self.api_key) self.event = { 'Message-Id': u'', 'X-Mailgun-Sid': u'WyIwNjgxZSIsICJ0b0BleGFtcGxlLmNvbSIsICI3MG' @@ -51,6 +62,26 @@ class TestMailgun(TransactionCase): 'os_family': False, 'ua_family': False, } + self.partner = self.env['res.partner'].create({ + 'name': 'Mr. Odoo', + 'email': 'mrodoo@example.com', + }) + self.response = { + "items": [{ + "log-level": "info", + "id": "oXAVv5URCF-dKv8c6Sa7T", + "timestamp": 1509119329.0, + "message": { + "headers": { + "to": "test@test.com", + "message-id": "test-id@f187c54734e8", + "from": "Mr. Odoo ", + "subject": "This is a test" + }, + }, + "event": "delivered" + }] + } def event_search(self, event_type): event = self.env['mail.tracking.event'].search([ @@ -63,6 +94,14 @@ class TestMailgun(TransactionCase): def test_no_api_key(self): self.env['ir.config_parameter'].set_param('mailgun.apikey', '') self.test_event_delivered() + with self.assertRaises(ValidationError): + self.env['mail.tracking.email']._mailgun_values() + + def test_no_domain(self): + self.env['ir.config_parameter'].set_param('mail.catchall.domain', '') + self.test_event_delivered() + with self.assertRaises(ValidationError): + self.env['mail.tracking.email']._mailgun_values() @mute_logger('odoo.addons.mail_tracking_mailgun.models' '.mail_tracking_email') @@ -288,3 +327,93 @@ class TestMailgun(TransactionCase): self.assertEqual(event.error_type, reason) self.assertEqual(event.error_description, code) self.assertEqual(event.error_details, description) + + @mock.patch(_packagepath + '.models.res_partner.requests') + def test_email_validity(self, mock_request): + self.partner.email_bounced = False + self.partner.email = 'info@tecnativa.com' + mock_request.get.return_value.apparent_encoding = 'ascii' + mock_request.get.return_value.status_code = 200 + mock_request.get.return_value.content = json.dumps({ + 'is_valid': True, + 'mailbox_verification': 'true', + }, ensure_ascii=True) + self.partner.check_email_validity() + self.assertFalse(self.partner.email_bounced) + self.partner.email = 'xoxoxoxo@tecnativa.com' + # Not a valid mailbox + mock_request.get.return_value.content = json.dumps({ + 'is_valid': True, + 'mailbox_verification': 'false', + }, ensure_ascii=True) + with self.assertRaises(UserError): + self.partner.check_email_validity() + # Not a valid mail address + mock_request.get.return_value.content = json.dumps({ + 'is_valid': False, + 'mailbox_verification': 'false', + }, ensure_ascii=True) + with self.assertRaises(UserError): + self.partner.check_email_validity() + # Unable to fully validate + mock_request.get.return_value.content = json.dumps({ + 'is_valid': True, + 'mailbox_verification': 'unknown', + }, ensure_ascii=True) + with self.assertRaises(UserError): + self.partner.check_email_validity() + self.assertTrue(self.partner.email_bounced) + + @mock.patch(_packagepath + '.models.res_partner.requests') + def test_email_validity_exceptions(self, mock_request): + mock_request.get.return_value.status_code = 404 + with self.assertRaises(UserError): + self.partner.check_email_validity() + self.env['ir.config_parameter'].set_param('mailgun.validation_key', '') + with self.assertRaises(UserError): + self.partner.check_email_validity() + + @mock.patch(_packagepath + '.models.res_partner.requests') + def test_bounced(self, mock_request): + self.partner.email_bounced = True + mock_request.get.return_value.status_code = 404 + self.partner.check_email_bounced() + self.assertFalse(self.partner.email_bounced) + mock_request.get.return_value.status_code = 200 + self.partner.force_set_bounced() + self.partner.check_email_bounced() + self.assertTrue(self.partner.email_bounced) + mock_request.delete.return_value.status_code = 200 + self.partner.force_unset_bounced() + self.assertFalse(self.partner.email_bounced) + + def test_email_bounced_set(self): + message_number = len(self.partner.message_ids) + 1 + self.partner._email_bounced_set('test_error', self.event) + self.assertEqual(len(self.partner.message_ids), message_number) + self.partner.email = "" + self.partner._email_bounced_set('test_error', self.event) + self.assertEqual(len(self.partner.message_ids), message_number) + + @mock.patch(_packagepath + '.models.mail_tracking_email.requests') + def test_manual_check(self, mock_request): + mock_request.get.return_value.content = json.dumps(self.response, + ensure_ascii=True) + mock_request.get.return_value.apparent_encoding = 'ascii' + mock_request.get.return_value.status_code = 200 + self.tracking_email.action_manual_check_mailgun() + event = self.env['mail.tracking.event'].search( + [('mailgun_id', '=', self.response['items'][0]['id'])]) + self.assertEqual(event.event_type, self.response['items'][0]['event']) + + @mock.patch(_packagepath + '.models.mail_tracking_email.requests') + def test_manual_check_exceptions(self, mock_request): + mock_request.get.return_value.status_code = 404 + with self.assertRaises(ValidationError): + self.tracking_email.action_manual_check_mailgun() + mock_request.get.return_value.status_code = 200 + mock_request.get.return_value.content = json.dumps('{}', + ensure_ascii=True) + mock_request.get.return_value.apparent_encoding = 'ascii' + with self.assertRaises(ValidationError): + self.tracking_email.action_manual_check_mailgun() diff --git a/mail_tracking_mailgun/views/mail_tracking_email.xml b/mail_tracking_mailgun/views/mail_tracking_email.xml new file mode 100644 index 0000000..f796106 --- /dev/null +++ b/mail_tracking_mailgun/views/mail_tracking_email.xml @@ -0,0 +1,16 @@ + + + + + Manual Mailgun check + mail.tracking.email + + + +