[10.0][IMP] mail_tracking_mailgun: add partner mail checks
This commit is contained in:
parent
3e8273581b
commit
3ca3ee4e49
@ -29,9 +29,16 @@ You must configure Mailgun webhooks in order to receive mail events:
|
|||||||
Replace '<your_domain>' with your Odoo install domain name
|
Replace '<your_domain>' with your Odoo install domain name
|
||||||
and '<your_database>' with your database name.
|
and '<your_database>' with your database name.
|
||||||
|
|
||||||
In order to validate Mailgun webhooks you have to save Mailgun api_key in
|
In order to validate Mailgun webhooks you have to configure the following system
|
||||||
a system parameter named 'mailgun.apikey'. You can find Mailgun api_key in your
|
parameters:
|
||||||
validated sending domain.
|
|
||||||
|
- `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
|
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
|
see a more accurate information, like the 'Received' or 'Bounced' status, which are
|
||||||
not usually detected by normal SMTP servers.
|
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
|
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
|
||||||
:alt: Try me on Runbot
|
:alt: Try me on Runbot
|
||||||
:target: https://runbot.odoo-community.org/runbot/205/9.0
|
:target: https://runbot.odoo-community.org/runbot/205/9.0
|
||||||
@ -70,6 +88,8 @@ Contributors
|
|||||||
|
|
||||||
* Antonio Espinosa <antonio.espinosa@tecnativa.com>
|
* Antonio Espinosa <antonio.espinosa@tecnativa.com>
|
||||||
* Carlos Dauden <carlos.dauden@tecnativa.com>
|
* Carlos Dauden <carlos.dauden@tecnativa.com>
|
||||||
|
* Pedro M. Baeza <pedro.baeza@tecnativa.com>
|
||||||
|
* David Vidal <david.vidal@tecnativa.com>
|
||||||
* Rafael Blasco <rafael.blasco@tecnativa.com>
|
* Rafael Blasco <rafael.blasco@tecnativa.com>
|
||||||
|
|
||||||
Maintainer
|
Maintainer
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com>
|
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com>
|
# 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).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
{
|
{
|
||||||
"name": "Mail tracking for Mailgun",
|
"name": "Mail tracking for Mailgun",
|
||||||
"summary": "Mail tracking and Mailgun webhooks integration",
|
"summary": "Mail tracking and Mailgun webhooks integration",
|
||||||
"version": "10.0.1.0.0",
|
"version": "10.0.1.1.0",
|
||||||
"category": "Social Network",
|
"category": "Social Network",
|
||||||
"website": "https://odoo-community.org/",
|
"website": "https://odoo-community.org/",
|
||||||
"author": "Tecnativa, "
|
"author": "Tecnativa, "
|
||||||
@ -15,4 +18,8 @@
|
|||||||
"depends": [
|
"depends": [
|
||||||
"mail_tracking",
|
"mail_tracking",
|
||||||
],
|
],
|
||||||
|
"data": [
|
||||||
|
"views/res_partner.xml",
|
||||||
|
"views/mail_tracking_email.xml",
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
@ -8,17 +8,138 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Odoo Server 8.0\n"
|
"Project-Id-Version: Odoo Server 8.0\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2016-09-02 02:43+0000\n"
|
"POT-Creation-Date: 2017-11-22 09:11+0000\n"
|
||||||
"PO-Revision-Date: 2016-09-02 02:43+0000\n"
|
"PO-Revision-Date: 2017-11-22 09:11+0000\n"
|
||||||
"Last-Translator: OCA Transbot <transbot@odoo-community.org>, 2016\n"
|
"Last-Translator: <david.vidal@tecnativa.com>\n"
|
||||||
"Language-Team: Spanish (https://www.transifex.com/oca/teams/23907/es/)\n"
|
"Language-Team: \n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: \n"
|
"Content-Transfer-Encoding: \n"
|
||||||
"Language: es\n"
|
"Plural-Forms: \n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\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
|
#. module: mail_tracking_mailgun
|
||||||
#: model:ir.model,name:mail_tracking_mailgun.model_mail_tracking_email
|
#: model:ir.model,name:mail_tracking_mailgun.model_mail_tracking_email
|
||||||
msgid "MailTracking email"
|
msgid "MailTracking email"
|
||||||
msgstr "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"
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com>
|
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
from . import ir_mail_server
|
from . import ir_mail_server
|
||||||
from . import mail_tracking_email
|
from . import mail_tracking_email
|
||||||
|
from . import mail_tracking_event
|
||||||
|
from . import res_partner
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com>
|
# Copyright 2016 Tecnativa - Antonio Espinosa
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com>
|
# Copyright 2016 Tecnativa - Antonio Espinosa
|
||||||
|
# Copyright 2017 Tecnativa - David Vidal
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import hmac
|
import hmac
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from odoo import models, api, fields
|
from odoo import _, api, fields, models
|
||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
@ -40,6 +44,7 @@ class MailTrackingEmail(models.Model):
|
|||||||
'complained': 'spam',
|
'complained': 'spam',
|
||||||
'bounced': 'hard_bounce',
|
'bounced': 'hard_bounce',
|
||||||
'dropped': 'reject',
|
'dropped': 'reject',
|
||||||
|
'accepted': 'sent',
|
||||||
}
|
}
|
||||||
|
|
||||||
def _mailgun_event_type_verify(self, event):
|
def _mailgun_event_type_verify(self, event):
|
||||||
@ -58,6 +63,19 @@ class MailTrackingEmail(models.Model):
|
|||||||
msg='{}{}'.format(str(timestamp), str(token)),
|
msg='{}{}'.format(str(timestamp), str(token)),
|
||||||
digestmod=hashlib.sha256).hexdigest()
|
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):
|
def _mailgun_signature_verify(self, event):
|
||||||
event = event or {}
|
event = event or {}
|
||||||
api_key = self.env['ir.config_parameter'].get_param('mailgun.apikey')
|
api_key = self.env['ir.config_parameter'].get_param('mailgun.apikey')
|
||||||
@ -104,6 +122,7 @@ class MailTrackingEmail(models.Model):
|
|||||||
'timestamp': ts,
|
'timestamp': ts,
|
||||||
'time': fields.Datetime.to_string(dt),
|
'time': fields.Datetime.to_string(dt),
|
||||||
'date': fields.Date.to_string(dt),
|
'date': fields.Date.to_string(dt),
|
||||||
|
'mailgun_id': event.get('id', False)
|
||||||
})
|
})
|
||||||
# Common field mapping
|
# Common field mapping
|
||||||
mapping = {
|
mapping = {
|
||||||
@ -192,3 +211,38 @@ class MailTrackingEmail(models.Model):
|
|||||||
else:
|
else:
|
||||||
_logger.info("Mailgun: event process '%s'", res)
|
_logger.info("Mailgun: event process '%s'", res)
|
||||||
return 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)
|
||||||
|
21
mail_tracking_mailgun/models/mail_tracking_event.py
Normal file
21
mail_tracking_mailgun/models/mail_tracking_event.py
Normal file
@ -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
|
135
mail_tracking_mailgun/models/res_partner.py
Normal file
135
mail_tracking_mailgun/models/res_partner.py
Normal file
@ -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
|
@ -1,5 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com>
|
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
from . import test_mailgun
|
from . import test_mailgun
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com>
|
# Copyright 2016 Tecnativa - Antonio Espinosa
|
||||||
|
# Copyright 2017 Tecnativa - David Vidal
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
from odoo.tools import mute_logger
|
from odoo.tools import mute_logger
|
||||||
from odoo.tests.common import TransactionCase
|
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):
|
class TestMailgun(TransactionCase):
|
||||||
@ -26,12 +32,17 @@ class TestMailgun(TransactionCase):
|
|||||||
self.recipient = u'to@example.com'
|
self.recipient = u'to@example.com'
|
||||||
self.mail, self.tracking_email = self.mail_send()
|
self.mail, self.tracking_email = self.mail_send()
|
||||||
self.api_key = u'key-12345678901234567890123456789012'
|
self.api_key = u'key-12345678901234567890123456789012'
|
||||||
|
self.domain = u'example.com'
|
||||||
self.token = u'f1349299097a51b9a7d886fcb5c2735b426ba200ada6e9e149'
|
self.token = u'f1349299097a51b9a7d886fcb5c2735b426ba200ada6e9e149'
|
||||||
self.timestamp = u'1471021089'
|
self.timestamp = u'1471021089'
|
||||||
self.signature = ('4fb6d4dbbe10ce5d620265dcd7a3c0b8'
|
self.signature = ('4fb6d4dbbe10ce5d620265dcd7a3c0b8'
|
||||||
'ca0dede1433103891bc1ae4086e9d5b2')
|
'ca0dede1433103891bc1ae4086e9d5b2')
|
||||||
self.env['ir.config_parameter'].set_param(
|
self.env['ir.config_parameter'].set_param(
|
||||||
'mailgun.apikey', self.api_key)
|
'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 = {
|
self.event = {
|
||||||
'Message-Id': u'<xxx.xxx.xxx-openerp-xxx-res.partner@test_db>',
|
'Message-Id': u'<xxx.xxx.xxx-openerp-xxx-res.partner@test_db>',
|
||||||
'X-Mailgun-Sid': u'WyIwNjgxZSIsICJ0b0BleGFtcGxlLmNvbSIsICI3MG'
|
'X-Mailgun-Sid': u'WyIwNjgxZSIsICJ0b0BleGFtcGxlLmNvbSIsICI3MG'
|
||||||
@ -51,6 +62,26 @@ class TestMailgun(TransactionCase):
|
|||||||
'os_family': False,
|
'os_family': False,
|
||||||
'ua_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 <mrodoo@odoo.com>",
|
||||||
|
"subject": "This is a test"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"event": "delivered"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
def event_search(self, event_type):
|
def event_search(self, event_type):
|
||||||
event = self.env['mail.tracking.event'].search([
|
event = self.env['mail.tracking.event'].search([
|
||||||
@ -63,6 +94,14 @@ class TestMailgun(TransactionCase):
|
|||||||
def test_no_api_key(self):
|
def test_no_api_key(self):
|
||||||
self.env['ir.config_parameter'].set_param('mailgun.apikey', '')
|
self.env['ir.config_parameter'].set_param('mailgun.apikey', '')
|
||||||
self.test_event_delivered()
|
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'
|
@mute_logger('odoo.addons.mail_tracking_mailgun.models'
|
||||||
'.mail_tracking_email')
|
'.mail_tracking_email')
|
||||||
@ -288,3 +327,93 @@ class TestMailgun(TransactionCase):
|
|||||||
self.assertEqual(event.error_type, reason)
|
self.assertEqual(event.error_type, reason)
|
||||||
self.assertEqual(event.error_description, code)
|
self.assertEqual(event.error_description, code)
|
||||||
self.assertEqual(event.error_details, description)
|
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()
|
||||||
|
16
mail_tracking_mailgun/views/mail_tracking_email.xml
Normal file
16
mail_tracking_mailgun/views/mail_tracking_email.xml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="mailgun_manual_check" model="ir.ui.view">
|
||||||
|
<field name="name">Manual Mailgun check</field>
|
||||||
|
<field name="model">mail.tracking.email</field>
|
||||||
|
<field name="inherit_id" ref="mail_tracking.view_mail_tracking_email_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="state" position="before">
|
||||||
|
<button name="action_manual_check_mailgun"
|
||||||
|
type="object" string="Re-sync Mailgun"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
28
mail_tracking_mailgun/views/res_partner.xml
Normal file
28
mail_tracking_mailgun/views/res_partner.xml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright 2016 Tecnativa - Pedro M. Baeza
|
||||||
|
Copyright 2016 Carlos Dauden - Tecnativa <carlos.dauden@tecnativa.com>
|
||||||
|
Copyright 2017 Tecnativa <vicent.cubellsn@tecnativa.com>
|
||||||
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -->
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="view_partner_form_mailgun" model="ir.ui.view">
|
||||||
|
<field name="name">Partner Mailgun button</field>
|
||||||
|
<field name="model">res.partner</field>
|
||||||
|
<field name="inherit_id" ref="mail_tracking.view_partner_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="email_bounced" position="after">
|
||||||
|
<label for="check_email_bounced" string="Mailgun"
|
||||||
|
attrs="{'invisible': [('email', '=', False)]}"/>
|
||||||
|
<group col="3" name="mailgun_buttons" attrs="{'invisible': [('email', '=', False)]}">
|
||||||
|
<button name="check_email_bounced" type="object" string="Check Mailgun" class="oe_link"/>
|
||||||
|
<button name="check_email_validity" type="object" string="Check email validity" class="oe_link"/>
|
||||||
|
<button name="force_set_bounced" type="object" string="Set Bounced" class="oe_link"
|
||||||
|
attrs="{'invisible': [('email_bounced', '=', True)]}"/>
|
||||||
|
<button name="force_unset_bounced" type="object" string="Unset Bounced" class="oe_link"
|
||||||
|
attrs="{'invisible': [('email_bounced', '=', False)]}"/>
|
||||||
|
</group>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
Loading…
Reference in New Issue
Block a user