def test_update_email_smtp_fail(self, mocked_add_status_msg): data = { 'email': '*****@*****.**', } request = fake_request(form_data=data, method='POST') # get the form to edit partner1 form = self.get_form('cms.form.my.account', sudo_uid=self.user1.id, req=request, main_object=self.partner1) form.o_request.website = self.env['website'].browse(1) with mock.patch.object(type(form), '_logout_and_notify') as mocked: with mock.patch.object( type(form), '_handle_login_update', **{'side_effect': MailDeliveryException('err', 'val')}): form.form_process() mocked.assert_not_called()
def send(self, auto_commit=False, raise_exception=False): """ 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 """ for server_id, batch_ids in self._split_by_server(): smtp_session = None try: smtp_session = self.env['ir.mail_server'].connect( mail_server_id=server_id) except Exception as exc: if raise_exception: # To be consistent and backward compatible with mail_mail.send() raised # exceptions, it is encapsulated into an Odoo MailDeliveryException raise MailDeliveryException( _('Unable to connect to SMTP Server'), exc) else: self.browse(batch_ids).write({ 'state': 'exception', 'failure_reason': exc }) else: self.browse(batch_ids)._send(auto_commit=auto_commit, raise_exception=raise_exception, smtp_session=smtp_session) _logger.info('Sent batch %s emails via mail server ID #%s', len(batch_ids), server_id) finally: if smtp_session: smtp_session.quit()
def send(self, auto_commit=False, raise_exception=False): """ 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 """ IrMailServer = self.env['ir.mail_server'] domain_link = self.env['ir.config_parameter'].get_param('web.base.url') for mail_id in self.ids: try: mail = self.browse(mail_id) # 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 = self.env['ir.model'].sudo().search([ ('model', '=', mail.model) ])[0] else: model = None if model: mail = mail.with_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. attachments = [(a['datas_fname'], base64.b64decode(a['datas'])) for a in mail.attachment_ids.sudo().read( ['datas_fname', 'datas'])] # specific behavior to customize the send email for notified partners email_list = [] if mail.email_to: email_list.append(mail.send_get_email_dict()) for partner in mail.recipient_ids: email_data = mail.send_get_email_dict(partner=partner) email_data.update({'partner_id': partner.id}) email_list.append(email_data) # headers headers = {} bounce_alias = self.env['ir.config_parameter'].get_param( "mail.bounce.alias") catchall_domain = self.env['ir.config_parameter'].get_param( "mail.catchall.domain") 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(safe_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', 'failure_reason': _('Error without exception. Probably due do sending an email without computed recipients.' ), }) mail_sent = False # build an RFC2822 email.message.Message object and send it without queuing res = None for email in email_list: body = email.get('body') for attachment in mail.attachment_ids: data = { 'mail_id': mail.id, 'crete_uid': 1, 'attachment_id': str(attachment.id), 'partner_id': email.get('partner_id'), 'email_to': email.get('email_to') and email.get('email_to')[0] or False, } queue = self.env['otp.link.queue'].create(data) link_download = domain_link + '''/otp/download/%s''' % str( queue.id) fname = attachment.datas_fname or '' body += "\n<a href='" + link_download + """' style='display: inline-block; margin-top: 8px; white-space: nowrap; padding: 6px 15px; font-size: 14px; line-height: 1.42857143; border-radius: 2px; text-decoration: none; color: #fff; border-color: #21b799; background-color: #21b799; font-weight:bold'>Click to download """ + fname + "</a><br/>" msg = IrMailServer.build_email( email_from=mail.email_from, email_to=email.get('email_to'), subject=mail.subject, body=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 ('%s-%s' % (mail.res_id, mail.model)), subtype='html', subtype_alternative='plain', headers=headers) try: res = IrMailServer.send_email( msg, mail_server_id=mail.mail_server_id.id) except AssertionError as error: if error.message == IrMailServer.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.info( "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, 'failure_reason': False }) mail_sent = True # /!\ can't use mail.state here, as mail.refresh() will cause an error # see revid:[email protected] in 6.1 if mail_sent: _logger.info( 'Mail with ID %r and Message-Id %r successfully sent', mail.id, mail.message_id) mail._postprocess_sent_message(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 psycopg2.Error: # If an error with the database occurs, chances are that the cursor is unusable. # This will lead to an `psycopg2.InternalError` being raised when trying to write # `state`, shadowing the original exception and forbid a retry on concurrent # update. Let's bubble it. raise except Exception as e: failure_reason = tools.ustr(e) _logger.exception('failed sending mail (id: %s) due to %s', mail.id, failure_reason) mail.write({ 'state': 'exception', 'failure_reason': failure_reason }) mail._postprocess_sent_message(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: self._cr.commit() return True
def send(self, auto_commit=False, raise_exception=False): """ 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 """ server_email_line = self.env['ir.mail_server.email.line'] default_outgoing = self.env['ir.mail_server'] IrMailServer = self.env['ir.mail_server.outgoing'] mail_server_id = False for mail_id in self.ids: try: mail = self.browse(mail_id) email_from = parseaddr(mail.email_from)[1] # Check Email From Helpdesk and check case for AWS ESE email if mail.model and mail.model == 'helpdesk.ticket' and mail.res_id: try: mail_server_ids = server_email_line.sudo().search([ ('name', '=', email_from) ]).mapped('server_id') mail_server_id = mail_server_ids.sorted( key=lambda r: r.sequence)[0].id IrMailServer = self.env['ir.mail_server'] except: raise Warning( 'Please config Outgoing Mail for account %s' % (email_from)) else: if self._uid: user_id = self.env['res.users'].browse(self._uid) if user_id.outgoing_mail_server and user_id.outgoing_mail_server.id: mail_server_id = user_id.outgoing_mail_server.id mail_server_ids = server_email_line.sudo().search([ ('name', '=', email_from) ]).mapped('server_id') server_ids = mail_server_ids.sorted( key=lambda r: r.sequence) if server_ids and len(server_ids) > 0: mail_server_id = server_ids[0].id IrMailServer = self.env['ir.mail_server'] # 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 = self.env['ir.model'].sudo().search([ ('model', '=', mail.model) ])[0] else: model = None if model: mail = mail.with_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. attachments = [(a['datas_fname'], base64.b64decode(a['datas'])) for a in mail.attachment_ids.sudo().read( ['datas_fname', 'datas'])] # specific behavior to customize the send email for notified partners email_list = [] if mail.email_to: email_list.append(mail.send_get_email_dict()) for partner in mail.recipient_ids: email_list.append( mail.send_get_email_dict(partner=partner)) # headers headers = {} bounce_alias = self.env['ir.config_parameter'].get_param( "mail.bounce.alias") catchall_domain = self.env['ir.config_parameter'].get_param( "mail.catchall.domain") 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) headers['Return-Path'] = mail.email_from if mail.headers: try: headers.update(safe_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', 'failure_reason': _('Error without exception. Probably due do sending an email without computed recipients.' ), }) mail_sent = False email_cc = '' if mail.email_cc: email_cc = mail.email_cc if mail.email_cc_partner and len(mail.email_cc_partner) > 0: for partner_id in mail.email_cc_partner: if email_cc == '': email_cc += partner_id.email or '' else: email_cc += ',' + partner_id.email or '' # build an RFC2822 email.message.Message object and send it without queuing res = None for email in email_list: msg = IrMailServer.build_email( email_from=mail.email_from, email_to=email.get('email_to'), subject=mail.subject, body=email.get('body'), body_alternative=email.get('body_alternative'), email_cc=tools.email_split(email_cc), reply_to=mail.reply_to, attachments=attachments, message_id=mail.message_id, references=mail.references, object_id=mail.res_id and ('%s-%s' % (mail.res_id, mail.model)), subtype='html', subtype_alternative='plain', headers=headers) try: res = IrMailServer.send_email( msg, mail_server_id=mail_server_id) except AssertionError as error: if error.message == IrMailServer.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.info( "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, 'failure_reason': False }) mail_sent = True # /!\ can't use mail.state here, as mail.refresh() will cause an error # see revid:[email protected] in 6.1 if mail_sent: _logger.info( 'Mail with ID %r and Message-Id %r successfully sent', mail.id, mail.message_id) mail._postprocess_sent_message(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 psycopg2.Error: # If an error with the database occurs, chances are that the cursor is unusable. # This will lead to an `psycopg2.InternalError` being raised when trying to write # `state`, shadowing the original exception and forbid a retry on concurrent # update. Let's bubble it. raise except Exception as e: failure_reason = tools.ustr(e) _logger.exception('failed sending mail (id: %s) due to %s', mail.id, failure_reason) mail.write({ 'state': 'exception', 'failure_reason': failure_reason }) mail._postprocess_sent_message(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: self._cr.commit() return True
def send(self, auto_commit=False, raise_exception=False): IrMailServer = self.env['ir.mail_server'] for mail in self: 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 = self.env['ir.model'].sudo().search([ ('model', '=', mail.model) ])[0] else: model = None if model: mail = mail.with_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. attachments = [(a['datas_fname'], base64.b64decode(a['datas'])) for a in mail.attachment_ids.sudo().read( ['datas_fname', 'datas'])] # specific behavior to customize the send email for notified partners email_list = [] if mail.email_to: email_list.append(mail.send_get_email_dict()) for partner in mail.recipient_ids: email_list.append( mail.send_get_email_dict(partner=partner)) # email cc email_cc = '' for partner in mail.email_partner_cc: email_cc += partner.email + ';' #email bcc email_bcc = '' for partner in mail.email_partner_bcc: email_bcc += partner.email + ';' # headers headers = {} bounce_alias = self.env['ir.config_parameter'].get_param( "mail.bounce.alias") catchall_domain = self.env['ir.config_parameter'].get_param( "mail.catchall.domain") 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', 'failure_reason': _('Error without exception. Probably due do sending an email without computed recipients.' ), }) mail_sent = False # build an RFC2822 email.message.Message object and send it without queuing res = None for email in email_list: msg = IrMailServer.build_email( email_from=mail.email_from, email_to=email.get('email_to'), subject=mail.subject, body=email.get('body'), body_alternative=email.get('body_alternative'), email_cc=tools.email_split(email_cc), email_bcc=tools.email_split(email_bcc), reply_to=mail.reply_to, attachments=attachments, message_id=mail.message_id, references=mail.references, object_id=mail.res_id and ('%s-%s' % (mail.res_id, mail.model)), subtype='html', subtype_alternative='plain', headers=headers) try: res = IrMailServer.send_email( msg, mail_server_id=mail.mail_server_id.id) except AssertionError as error: if error.message == IrMailServer.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.info( "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, 'failure_reason': False }) mail_sent = True # /!\ can't use mail.state here, as mail.refresh() will cause an error # see revid:[email protected] in 6.1 if mail_sent: _logger.info( 'Mail with ID %r and Message-Id %r successfully sent', mail.id, mail.message_id) mail._postprocess_sent_message(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 psycopg2.Error: # If an error with the database occurs, chances are that the cursor is unusable. # This will lead to an `psycopg2.InternalError` being raised when trying to write # `state`, shadowing the original exception and forbid a retry on concurrent # update. Let's bubble it. raise except Exception as e: failure_reason = tools.ustr(e) _logger.exception('failed sending mail (id: %s) due to %s', mail.id, failure_reason) mail.write({ 'state': 'exception', 'failure_reason': failure_reason }) mail._postprocess_sent_message(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: self._cr.commit() return True
def send(self, auto_commit=False, raise_exception=False): """ 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 """ ir_mail_server = self.env['ir.mail_server'] ir_values = self.env['ir.values'] default_mail_server = ir_values.get_default('mail.mail', 'mail_server_id') existing_ir_mail_server = self.env['ir.mail_server'].sudo().search([ ('id', '=', default_mail_server) ]) if len(existing_ir_mail_server) > 0: default_mail_address = existing_ir_mail_server.smtp_user else: default_mail_address = False for mail in self.sudo().browse(self.ids): subject = mail.subject # hier müssen wir Betreff extra holen 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: # beide self.pool model_id = self.env['ir.model'].sudo().search([ ('model', '=', mail.model) ])[0] model = model_id else: model = None if model: mail = mail.with_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(self.sudo(), attachment_ids, # ['datas_fname', 'datas'])] attachments = [(a['datas_fname'], base64.b64decode(a['datas'])) for a in mail.attachment_ids.sudo().read( ['datas_fname', 'datas'])] # specific behavior to customize the send email for notified partners email_list = [] if mail.email_to: email_list.append(mail.send_get_email_dict()) for partner in mail.recipient_ids: email_list.append( mail.send_get_email_dict(partner=partner)) # headers headers = {} bounce_alias = self.env['ir.config_parameter'].get_param( "mail.bounce.alias") catchall_domain = self.env['ir.config_parameter'].get_param( "mail.catchall.domain") ### Übernahme der Anpassung vom Equitania Modul (siehe ReleaseNotes des Equitania Moduls vom 15.01.2016) if bounce_alias and catchall_domain: headers['Return-Path'] = '%s@%s' % (bounce_alias, catchall_domain) else: headers['Return-Path'] = mail.email_from #### Kern-Version # 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 context = {} user = context.get(self._uid, self.sudo()) if user != self.sudo(): mail_server = ir_mail_server.search([('user_id', '=', user) ]) else: partner_id = mail.author_id.id res_users_pool = self.env['res.users'] res_users_id = res_users_pool.search([('partner_id', '=', partner_id)]) mail_server = ir_mail_server.search([('user_id', '=', res_users_id.id)]) for email in email_list: if len(mail_server) == 0 and default_mail_address: msg = ir_mail_server.build_email( email_from=default_mail_address, email_to=email.get('email_to'), #subject=email.get('subject'), # alte Version subject= subject, # neue Version - nur so können wir Betreff korrekt übergeben body=email.get('body'), body_alternative=email.get('body_alternative'), email_cc=tools.email_split(mail.email_cc), reply_to=mail.email_from, attachments=attachments, message_id=mail.message_id, references=mail.references, object_id=mail.res_id and ('%s-%s' % (mail.res_id, mail.model)), subtype='html', subtype_alternative='plain', headers=headers) msg['Return-Path'] = default_mail_address res = ir_mail_server.send_email( msg, mail_server_id=default_mail_server, ) else: msg = ir_mail_server.build_email( email_from=mail.email_from, 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.email_from, attachments=attachments, message_id=mail.message_id, references=mail.references, object_id=mail.res_id and ('%s-%s' % (mail.res_id, mail.model)), subtype='html', subtype_alternative='plain', headers=headers) msg['Return-Path'] = mail.email_from if len(mail_server) == 0: pass else: res = ir_mail_server.send_email( msg, mail_server_id=mail_server[0].id, ) 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:[email protected] 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(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(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: self._cr.commit() return True
def _send( self, auto_commit=False, raise_exception=False, smtp_session=None, ): IrMailServer = self.env["ir.mail_server"] for mail_id in self.ids: try: mail = self.browse(mail_id) if mail.state != "outgoing": if mail.state != "exception" and mail.auto_delete: mail.sudo().unlink() continue # 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 = self.env["ir.model"]._get(mail.model)[0] else: model = None if model: mail = mail.with_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. attachments = [( a["datas_fname"], base64.b64decode(a["datas"]), a["mimetype"], ) for a in mail.attachment_ids.sudo().read( ["datas_fname", "datas", "mimetype"])] # specific behavior to customize the send # email for notified partners email_list = [] if mail.email_to: email_list.append(mail.send_get_email_dict()) for partner in mail.recipient_ids: email_list.append( mail.send_get_email_dict(partner=partner)) _logger.info("email_list? %r ", email_list) # headers headers = {} ICP = self.env["ir.config_parameter"].sudo() bounce_alias = ICP.get_param("mail.bounce.alias") catchall_domain = ICP.get_param("mail.catchall.domain") 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(safe_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", "failure_reason": _("Error without exception. Probably due do sending an email without computed recipients." ), }) mail_sent = False # build an RFC2822 email.message.Message object # and send it without queuing res = None for email in email_list[:1]: if mail.email_from not in email.get("email_cc"): _logger.info( "must assign to cc list %r ", mail.email_from, ) else: _logger.info( "from is assigned to cc list %r ", mail.email_from, ) _logger.info("email_from %r ", mail.email_from) _logger.info("email_to %r ", email.get("email_to")) _logger.info("email_cc %r ", email.get("email_cc")) _logger.info("email_replay_to %r ", mail.reply_to) _logger.info("email_subhject %r ", mail.subject) msg = IrMailServer.build_email( email_from=mail.email_from, email_to=email.get("email_to"), subject=mail.subject, body=email.get("body"), body_alternative=email.get("body_alternative"), email_cc=email.get("email_cc"), reply_to=mail.reply_to, attachments=attachments, message_id=mail.message_id, references=mail.references, object_id=mail.res_id and ("%s-%s" % (mail.res_id, mail.model)), subtype="html", subtype_alternative="plain", headers=headers, ) try: res = IrMailServer.send_email( msg, mail_server_id=mail.mail_server_id.id, smtp_session=smtp_session, ) except AssertionError as error: if (str(error) == IrMailServer.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.info( "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, "failure_reason": False, }) mail_sent = True # /!\ can't use mail.state here, as mail.refresh() # will cause an error # see revid:[email protected] # in 6.1 if mail_sent: _logger.info( "Mail with ID %r and Message-Id %r successfully sent", mail.id, mail.message_id, ) mail._postprocess_sent_message(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 psycopg2.Error: # If an error with the database occurs, chances are that # the cursor is unusable. # This will lead to an `psycopg2.InternalError` being # raised when trying to write # `state`, shadowing the original exception and forbid # a retry on concurrent # update. Let's bubble it. raise except Exception as e: failure_reason = tools.ustr(e) _logger.exception( "failed sending mail (id: %s) due to %s", mail.id, failure_reason, ) mail.write({ "state": "exception", "failure_reason": failure_reason, }) mail._postprocess_sent_message(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: self._cr.commit() return True
def send(self): # Get GCM Server Details gcm_server = None if self.gcm_server_id: gcm_server = self.gcm_server_id elif not gcm_server: gcm_server = self.env['google_gcm.server'].sudo().search( [('active', '=', True), ('state', '=', 'done')], limit=1, order='sequence') if not gcm_server: raise UserError( _("Missing GCM Server") + "\n" + _("Please re-compute server, or provide the GCM parameters explicitly." )) gcm_server.ensure_one() for gc_message in self: try: # specific behavior to customize the send gcm for notified partners gcm_device_list = [] for partner in gc_message.recipient_ids: for device in partner.gcm_device_ids: gcm_device_list.append(device.gcm_reg_id) if len(gcm_device_list) == 0: raise UserError( _("Missing Recipients") + "\n" + _("Please define recipients that own at least one registered device." )) # Writing on the gcm 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 gc_message.write({ 'state': 'exception', 'state_reason': _('Error without exception. Probably due do sending an google notification without computed recipients.' ), }) gcm = GCM(gcm_server.api_key) data = { 'Title': gc_message.send_get_gcm_body(gc_message.name), 'Content': gc_message.send_get_gcm_body(gc_message.content) } """response = gcm.json_request( registration_id = gcm_device_list, data = data, collapse_key = gcm_server.collapse_key, delay_while_idle = gcm_server.delay_while_idle, time_to_live = gcm_server.time_to_live ) """ message = JSONMessage( gcm_device_list, data, collapse_key=gcm_server.collapse_key, delay_while_idle=gcm_server.delay_while_idle, time_to_live=gcm_server.time_to_live, dry_run=True) response = gcm.send(message) state_reason = '' for reg_id, msg_id in response.success.items(): state_reason += '-Success for reg_id %s' % reg_id _logger.info( 'GCM Notification with Title %r successfully sent to device reg_id %r', gc_message.name, reg_id) if gc_message.auto_delete: _logger.info( "Deleting google_gcm.message %s :%s after success sending ('auto delete' option checked)", gc_message.id, gc_message.name) for reg_id in response.not_registered: device = self.env['google_gcm.device'].search([ ('gcm_reg_id', '=', reg_id) ]) _logger.info( "Deleting invalid recipients for google_gcm.message %s: %s", device.partner_id.name, device.gcm_reg_id) state_reason += "Deleting invalid recipients for google_gcm.message " + device.partner_id.name + ":" + device.gcm_reg_id device.unlink() for reg_id, err_code in response.failed.items(): device = self.env['google_gcm.device'].search([ ('gcm_reg_id', '=', reg_id) ]) _logger.info( "Deleting google_gcm.device %s: %s because %s", device.partner_id.name, device.gcm_reg_id, err_code) state_reason += "Deleting google_gcm.device " + device.partner_id.name + ":" + device.gcm_reg_id + " because " + err_code device.unlink() for reg_id, new_reg_id in response.canonical.items(): # Repace reg_id with canonical_id in your database device = self.env['google_gcm.device'].search([ ('gcm_reg_id', '=', reg_id) ]) _logger.info( "Updating recipients registration_id %s: %s to %s during google_gcm.message sent", device.partner_id.name, device.gcm_reg_id, new_reg_id) state_reason += "Updating recipients registration_id of " + device.partner_id.name + ":" + device.gcm_reg_id + " to " + new_reg_id + " during google_gcm.message sent" device.gcm_reg_id = new_reg_id if response.needs_retry(): # construct new message with only failed regids retry_msg = response.retry() # you have to wait before attemting again. delay() # will tell you how long to wait depending on your # current retry counter, starting from 0. _logger.info("Wait or schedule task after %s seconds", response.delay(retry)) state_reason += "Wait or schedule task after " + response.delay( retry) + " seconds" # retry += 1 and send retry_msg again gc_message.write({ 'state': 'sent', 'state_reason': state_reason }) 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 GCM Notification with ID %r and Title %r. Consider raising the --limit-memory-hard startup option', gc_message.id, gc_message.name) raise except psycopg2.Error: # If an error with the database occurs, chances are that the cursor is unusable. # This will lead to an `psycopg2.InternalError` being raised when trying to write # `state`, shadowing the original exception and forbid a retry on concurrent # update. Let's bubble it. raise except Exception as e: failure_reason = tools.ustr(e) _logger.exception( 'failed sending google notification (id: %s) due to %s', gc_message.id, failure_reason) gc_message.write({ 'state': 'exception', 'state_reason': failure_reason }) 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( _("Google Cloud Message Delivery Failed"), value) raise return True
def _send(self, auto_commit=False, raise_exception=False, smtp_session=None): IrMailServer = self.env['ir.mail_server'] for mail_id in self.ids: try: mail = self.browse(mail_id) if mail.state != 'outgoing': if mail.state != 'exception' and mail.auto_delete: mail.sudo().unlink() continue # 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 = self.env['ir.model']._get(mail.model)[0] else: model = None if model: mail = mail.with_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. attachments = [(a['datas_fname'], base64.b64decode(a['datas']), a['mimetype']) for a in mail.attachment_ids.sudo().read( ['datas_fname', 'datas', 'mimetype'])] # specific behavior to customize the send email for notified partners email_list = [] if mail.email_to: email_list.append(mail.send_get_email_dict()) for partner in mail.recipient_ids: email_list.append( mail.send_get_email_dict(partner=partner)) if mail.cc_visible: email_cc_list = [] for partner_cc in mail.recipient_cc_ids: email_to = formataddr( (partner_cc.name or 'False', partner_cc.email or 'False')) email_cc_list.append(email_to) # Convert Email List To String For BCC & CC email_cc_string = ','.join(email_cc_list) else: email_cc_string = '' if mail.bcc_visible: email_bcc_list = [] for partner_bcc in mail.recipient_bcc_ids: email_to = formataddr( (partner_bcc.name or 'False', partner_bcc.email or 'False')) email_bcc_list.append(email_to) # Convert Email List To String For BCC & CC email_bcc_string = ','.join(email_bcc_list) else: email_bcc_string = '' # headers headers = {} ICP = self.env['ir.config_parameter'].sudo() bounce_alias = ICP.get_param("mail.bounce.alias") catchall_domain = ICP.get_param("mail.catchall.domain") 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(safe_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', 'failure_reason': _('Error without exception. Probably due do sending an email without computed recipients.' ), }) mail_sent = False # Update notification in a transient exception state to avoid concurrent # update in case an email bounces while sending all emails related to current # mail record. notifs = self.env['mail.notification'].search([ ('is_email', '=', True), ('mail_message_id', 'in', mail.mapped('mail_message_id').ids), ('res_partner_id', 'in', mail.mapped('recipient_ids').ids), ('email_status', 'not in', ('sent', 'canceled')) ]) if notifs: notifs.sudo().write({ 'email_status': 'exception', }) # build an RFC2822 email.message.Message object and send it without queuing res = None for email in email_list: msg = IrMailServer.build_email( email_from=mail.email_from, email_to=email.get('email_to'), subject=mail.subject, body=email.get('body'), body_alternative=email.get('body_alternative'), email_cc=tools.email_split_and_format(email_cc_string), email_bcc=tools.email_split_and_format( email_bcc_string), reply_to=mail.reply_to, attachments=attachments, message_id=mail.message_id, references=mail.references, object_id=mail.res_id and ('%s-%s' % (mail.res_id, mail.model)), subtype='html', subtype_alternative='plain', headers=headers) try: res = IrMailServer.send_email( msg, mail_server_id=mail.mail_server_id.id, smtp_session=smtp_session) except AssertionError as error: if str(error) == IrMailServer.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.info( "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, 'failure_reason': False }) mail_sent = True # /!\ can't use mail.state here, as mail.refresh() will cause an error # see revid:[email protected] in 6.1 if mail_sent: _logger.info( 'Mail with ID %r and Message-Id %r successfully sent', mail.id, mail.message_id) mail._postprocess_sent_message(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 (psycopg2.Error, smtplib.SMTPServerDisconnected): # If an error with the database or SMTP session occurs, chances are that the cursor # or SMTP session are unusable, causing further errors when trying to save the state. _logger.exception( 'Exception while processing mail with ID %r and Msg-Id %r.', mail.id, mail.message_id) raise except Exception as e: failure_reason = tools.ustr(e) _logger.exception('failed sending mail (id: %s) due to %s', mail.id, failure_reason) mail.write({ 'state': 'exception', 'failure_reason': failure_reason }) mail._postprocess_sent_message(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: self._cr.commit() return True
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(safe_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 str(error) == 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:[email protected] 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