Antonio Espinosa
8 years ago
14 changed files with 684 additions and 9 deletions
-
10mail_tracking/controllers/main.py
-
3mail_tracking/models/ir_mail_server.py
-
64mail_tracking/models/mail_tracking_event.py
-
6mail_tracking/views/mail_tracking_event_view.xml
-
71mail_tracking_mailgun/README.rst
-
5mail_tracking_mailgun/__init__.py
-
18mail_tracking_mailgun/__openerp__.py
-
6mail_tracking_mailgun/models/__init__.py
-
22mail_tracking_mailgun/models/ir_mail_server.py
-
196mail_tracking_mailgun/models/mail_tracking_email.py
-
BINmail_tracking_mailgun/static/description/icon.png
-
6mail_tracking_mailgun/static/description/icon.svg
-
5mail_tracking_mailgun/tests/__init__.py
-
281mail_tracking_mailgun/tests/test_mailgun.py
@ -0,0 +1,71 @@ |
|||||
|
.. 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 |
||||
|
|
||||
|
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 this URL for each event you want to track: |
||||
|
|
||||
|
.. code:: html |
||||
|
|
||||
|
https://<your_domain>/mail/tracking/all/<your_database> |
||||
|
|
||||
|
Replace '<your_domain>' by your Odoo install domain name |
||||
|
and '<your_database>' by 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 |
||||
|
===== |
||||
|
|
||||
|
.. 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 |
||||
|
<https://github.com/OCA/social/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 <http://seeklogo.com/mailgun-logo-273630.html>`_. |
||||
|
|
||||
|
Contributors |
||||
|
------------ |
||||
|
|
||||
|
* Antonio Espinosa <antonio.espinosa@tecnativa.com> |
||||
|
|
||||
|
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. |
@ -0,0 +1,5 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
from . import models |
@ -0,0 +1,18 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com> |
||||
|
# 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", |
||||
|
], |
||||
|
} |
@ -0,0 +1,6 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
from . import ir_mail_server |
||||
|
from . import mail_tracking_email |
@ -0,0 +1,22 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
||||
|
|
||||
|
import json |
||||
|
import threading |
||||
|
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 = { |
||||
|
'odoo_db': getattr(threading.currentThread(), 'dbname', None), |
||||
|
'tracking_email_id': tracking_email_id, |
||||
|
} |
||||
|
headers['X-Mailgun-Variables'] = json.dumps(metadata) |
||||
|
return headers |
@ -0,0 +1,196 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
import hashlib |
||||
|
import hmac |
||||
|
import threading |
||||
|
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', '=ilike', country_code), |
||||
|
]) |
||||
|
if country: |
||||
|
return country.id |
||||
|
return False |
||||
|
|
||||
|
def _mailgun_mandatory_fields(self): |
||||
|
return ('event', 'timestamp', 'token', 'signature', |
||||
|
'tracking_email_id', 'odoo_db') |
||||
|
|
||||
|
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', |
||||
|
} |
||||
|
|
||||
|
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.info("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 = getattr(threading.currentThread(), 'dbname', None) |
||||
|
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)]) |
||||
|
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 |
After Width: 128 | Height: 128 | Size: 6.3 KiB |
@ -0,0 +1,6 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?> |
||||
|
<svg width="256px" height="256px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid"> |
||||
|
<g> |
||||
|
<path d="M127.997895,105.847663 C140.236194,105.847663 150.154742,115.765009 150.154742,127.999399 C150.154742,140.236194 140.236194,150.154742 127.997895,150.154742 C115.763806,150.154742 105.845258,140.233488 105.845258,127.999399 C105.845258,115.765009 115.763806,105.847663 127.997895,105.847663 L127.997895,105.847663 Z M52.5129516,127.999399 C52.5129516,86.3085405 86.3085405,52.5141543 127.997895,52.5141543 C169.692662,52.5141543 203.485846,86.3085405 203.485846,127.999399 C203.485846,130.759044 203.338512,133.481406 203.05046,136.159267 C202.477063,143.517821 207.877285,149.134533 215.188933,149.134533 C227.598018,149.134533 228.920411,133.134721 228.920411,127.999399 C228.920411,72.2595437 183.736547,27.0768826 127.997895,27.0768826 C72.259243,27.0768826 27.0768826,72.2595437 27.0768826,127.999399 C27.0768826,183.735345 72.259243,228.919209 127.997895,228.919209 C157.599292,228.919209 184.224251,216.18178 202.685735,195.88374 L223.433292,213.305771 C199.997651,239.503464 165.92303,256.003909 127.997895,256.003909 C57.3064005,256.003909 0,198.692096 0,127.999399 C0,57.3064005 57.3064005,0 127.997895,0 C198.693599,0 256,57.3064005 256,127.999399 C256,156.415212 242.469076,179.489736 215.29357,179.489736 C203.333401,179.489736 196.064148,174.009833 192.093662,167.893991 C178.763893,189.262454 155.045612,203.489755 127.997895,203.489755 C86.3085405,203.489755 52.5129516,169.688753 52.5129516,127.999399 L52.5129516,127.999399 Z M127.997895,79.5898342 C101.264089,79.5898342 79.5886315,101.261684 79.5886315,127.999399 C79.5886315,154.735911 101.264089,176.411369 127.997895,176.411369 C154.734407,176.411369 176.411369,154.735911 176.411369,127.999399 C176.411369,101.261684 154.734407,79.5898342 127.997895,79.5898342 L127.997895,79.5898342 Z" fill="#AF252A"></path> |
||||
|
</g> |
||||
|
</svg> |
@ -0,0 +1,5 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
from . import test_mailgun |
@ -0,0 +1,281 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com> |
||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). |
||||
|
|
||||
|
import threading |
||||
|
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': '<p>This is a test message</p>', |
||||
|
}) |
||||
|
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 = u'%s' % self.env['mail.tracking.email'].\ |
||||
|
_mailgun_signature(self.api_key, self.timestamp, self.token) |
||||
|
self.env['ir.config_parameter'].set_param( |
||||
|
'mailgun.apikey', self.api_key) |
||||
|
self.event = { |
||||
|
'Message-Id': u'<xxx.xxx.xxx-openerp-xxx-res.partner@test_db>', |
||||
|
'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': getattr(threading.currentThread(), 'dbname', None), |
||||
|
'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 = u'%s' % self.env['mail.tracking.email'].\ |
||||
|
_mailgun_signature(self.api_key, timestamp, self.token) |
||||
|
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) |
||||
|
|
||||
|
# 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) |
Write
Preview
Loading…
Cancel
Save
Reference in new issue