import base64 import logging import re from email.utils import formataddr from odoo import SUPERUSER_ID, tools from odoo.addons.base.ir.ir_mail_server import MailDeliveryException from odoo.osv import osv from odoo.tools.safe_eval import safe_eval as eval from odoo.tools.translate import _ _logger = logging.getLogger(__name__) class MailMail(osv.Model): _inherit = "mail.mail" def send( self, cr, uid, ids, auto_commit=False, raise_exception=False, context=None ): # copy-paste from addons/mail/mail_mail.py """ Sends the selected emails immediately, ignoring their current state (mails that have already been sent should not be passed unless they should actually be re-sent). Emails successfully delivered are marked as 'sent', and those that fail to be deliver are marked as 'exception', and the corresponding error mail is output in the server logs. :param bool auto_commit: whether to force a commit of the mail status after sending each mail (meant only for scheduler processing); should never be True during normal transactions (default: False) :param bool raise_exception: whether to raise an exception if the email sending process has failed :return: True """ # NEW STUFF catchall_alias = self.pool["ir.config_parameter"].get_param( cr, uid, "mail.catchall.alias_from", context=context ) catchall_alias_name = self.pool["ir.config_parameter"].get_param( cr, uid, "mail.catchall.name_alias_from", context=context ) catchall_domain = self.pool["ir.config_parameter"].get_param( cr, uid, "mail.catchall.domain", context=context ) correct_email_from = r"@%s>?\s*$" % catchall_domain default_email_from = "{}@{}".format(catchall_alias, catchall_domain) context = dict(context or {}) ir_mail_server = self.pool.get("ir.mail_server") ir_attachment = self.pool["ir.attachment"] for mail in self.browse(cr, SUPERUSER_ID, ids, context=context): try: # TDE note: remove me when model_id field is present on mail.message - done here to avoid doing it multiple times in the sub method if mail.model: model_id = self.pool["ir.model"].search( cr, SUPERUSER_ID, [("model", "=", mail.model)], context=context )[0] model = self.pool["ir.model"].browse( cr, SUPERUSER_ID, model_id, context=context ) else: model = None if model: context["model_name"] = model.name # load attachment binary data with a separate read(), as prefetching all # `datas` (binary field) could bloat the browse cache, triggerring # soft/hard mem limits with temporary data. attachment_ids = [a.id for a in mail.attachment_ids] attachments = [ (a["datas_fname"], base64.b64decode(a["datas"])) for a in ir_attachment.read( cr, SUPERUSER_ID, attachment_ids, ["datas_fname", "datas"] ) ] # specific behavior to customize the send email for notified partners email_list = [] if mail.email_to: email_list.append( self.send_get_email_dict(cr, uid, mail, context=context) ) for partner in mail.recipient_ids: email_list.append( self.send_get_email_dict( cr, uid, mail, partner=partner, context=context ) ) # headers headers = {} bounce_alias = self.pool["ir.config_parameter"].get_param( cr, uid, "mail.bounce.alias", context=context ) catchall_domain = self.pool["ir.config_parameter"].get_param( cr, uid, "mail.catchall.domain", context=context ) if bounce_alias and catchall_domain: if mail.model and mail.res_id: headers["Return-Path"] = "%s-%d-%s-%d@%s" % ( bounce_alias, mail.id, mail.model, mail.res_id, catchall_domain, ) else: headers["Return-Path"] = "%s-%d@%s" % ( bounce_alias, mail.id, catchall_domain, ) if mail.headers: try: headers.update(eval(mail.headers)) except Exception: pass # Writing on the mail object may fail (e.g. lock on user) which # would trigger a rollback *after* actually sending the email. # To avoid sending twice the same email, provoke the failure earlier mail.write({"state": "exception"}) mail_sent = False # build an RFC2822 email.message.Message object and send it without queuing res = None for email in email_list: # NEW STUFF email_from = mail.email_from if re.search(correct_email_from, email_from) is None: email_from = default_email_from if catchall_alias_name: email_from = formataddr((catchall_alias_name, email_from)) msg = ir_mail_server.build_email( email_from=email_from, # NEW STUFF email_to=email.get("email_to"), subject=email.get("subject"), body=email.get("body"), body_alternative=email.get("body_alternative"), email_cc=tools.email_split(mail.email_cc), reply_to=mail.reply_to, attachments=attachments, message_id=mail.message_id, references=mail.references, object_id=mail.res_id and ("{}-{}".format(mail.res_id, mail.model)), subtype="html", subtype_alternative="plain", headers=headers, ) try: res = ir_mail_server.send_email( cr, uid, msg, mail_server_id=mail.mail_server_id.id, context=context, ) except AssertionError as error: if error.message == ir_mail_server.NO_VALID_RECIPIENT: # No valid recipient found for this particular # mail item -> ignore error to avoid blocking # delivery to next recipients, if any. If this is # the only recipient, the mail will show as failed. _logger.warning( "Ignoring invalid recipients for mail.mail %s: %s", mail.message_id, email.get("email_to"), ) else: raise if res: mail.write({"state": "sent", "message_id": res}) mail_sent = True # /!\ can't use mail.state here, as mail.refresh() will cause an error # see revid:odo@openerp.com-20120622152536-42b2s28lvdv3odyr in 6.1 if mail_sent: _logger.info( "Mail with ID %r and Message-Id %r successfully sent", mail.id, mail.message_id, ) self._postprocess_sent_message( cr, uid, mail, context=context, mail_sent=mail_sent ) except MemoryError: # prevent catching transient MemoryErrors, bubble up to notify user or abort cron job # instead of marking the mail as failed _logger.exception( "MemoryError while processing mail with ID %r and Msg-Id %r. " "Consider raising the --limit-memory-hard startup option", mail.id, mail.message_id, ) raise except Exception as e: _logger.exception("failed sending mail.mail %s", mail.id) mail.write({"state": "exception"}) self._postprocess_sent_message( cr, uid, mail, context=context, mail_sent=False ) if raise_exception: if isinstance(e, AssertionError): # get the args of the original error, wrap into a value and throw a MailDeliveryException # that is an except_orm, with name and value as arguments value = ". ".join(e.args) raise MailDeliveryException(_("Mail Delivery Failed"), value) raise if auto_commit is True: cr.commit() return True