def send(self, cr, uid, ids, auto_commit=False, raise_exception=False, context=None): """ 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 """ 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: headers['Return-Path'] = '%s@%s' % (bounce_alias, catchall_domain) else: headers['Return-Path'] = mail.email_from #=========================================================== # 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: 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.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) #msg['Return-Path'] = mail.email_from 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:[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 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: _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
def send(self, cr, uid, ids, auto_commit=False, raise_exception=False, context=None): """ 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.pool.get('ir.mail_server') for mail in self.browse(cr, SUPERUSER_ID, ids, context=context): try: # handle attachments attachments = [] for attach in mail.attachment_ids: attachments.append( (attach.datas_fname, base64.b64decode(attach.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) # build an RFC2822 email.message.Message object and send it without queuing res = None for email in email_list: 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.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) res = ir_mail_server.send_email( cr, uid, msg, mail_server_id=mail.mail_server_id.id, context=context) if res: mail.write({'state': 'sent', 'message_id': res}) mail_sent = True else: mail.write({'state': 'exception'}) mail_sent = False # /!\ can't use mail.state here, as mail.refresh() will cause an error # see revid:[email protected] in 6.1 if mail_sent: self._postprocess_sent_message(cr, uid, mail, context=context) except Exception as e: _logger.exception('failed sending mail.mail %s', mail.id) mail.write({'state': 'exception'}) 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 == True: 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 """ 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)) # 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(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_v9(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: 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_v9(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_email(self, cr, uid, message, mail_server_id=None, smtp_server=None, smtp_port=None, smtp_user=None, smtp_password=None, smtp_encryption=None, smtp_debug=False, context=None): """Sends an email directly (no queuing). No retries are done, the caller should handle MailDeliveryException in order to ensure that the mail is never lost. If the mail_server_id is provided, sends using this mail server, ignoring other smtp_* arguments. If mail_server_id is None and smtp_server is None, use the default mail server (highest priority). If mail_server_id is None and smtp_server is not None, use the provided smtp_* arguments. If both mail_server_id and smtp_server are None, look for an 'smtp_server' value in server config, and fails if not found. :param message: the email.message.Message to send. The envelope sender will be extracted from the ``Return-Path`` or ``From`` headers. The envelope recipients will be extracted from the combined list of ``To``, ``CC`` and ``BCC`` headers. :param mail_server_id: optional id of ir.mail_server to use for sending. overrides other smtp_* arguments. :param smtp_server: optional hostname of SMTP server to use :param smtp_encryption: optional TLS mode, one of 'none', 'starttls' or 'ssl' (see ir.mail_server fields for explanation) :param smtp_port: optional SMTP port, if mail_server_id is not passed :param smtp_user: optional SMTP user, if mail_server_id is not passed :param smtp_password: optional SMTP password to use, if mail_server_id is not passed :param smtp_debug: optional SMTP debug flag, if mail_server_id is not passed :return: the Message-ID of the message that was just sent, if successfully sent, otherwise raises MailDeliveryException and logs root cause. """ smtp_from = message['Return-Path'] or message['From'] assert smtp_from, "The Return-Path or From header is required for any outbound email" # The email's "Envelope From" (Return-Path), and all recipient addresses must only contain ASCII characters. from_rfc2822 = extract_rfc2822_addresses(smtp_from) assert from_rfc2822, ( "Malformed 'Return-Path' or 'From' address: %r - " "It should contain one valid plain ASCII email") % smtp_from # use last extracted email, to support rarities like 'Support@MyComp <*****@*****.**>' smtp_from = from_rfc2822[-1] email_to = message['To'] email_cc = message['Cc'] email_bcc = message['Bcc'] smtp_to_list = filter( None, tools.flatten( map(extract_rfc2822_addresses, [email_to, email_cc, email_bcc]))) assert smtp_to_list, "At least one valid recipient address should be specified for outgoing emails (To/Cc/Bcc)" # Do not actually send emails in testing mode! if getattr(threading.currentThread(), 'testing', False): _test_logger.info("skip sending email in test mode") return message['Message-Id'] # Get SMTP Server Details from Mail Server mail_server = None if mail_server_id: mail_server = self.browse(cr, SUPERUSER_ID, mail_server_id) elif not smtp_server: mail_server_ids = self.search(cr, SUPERUSER_ID, [], order='sequence', limit=1) if mail_server_ids: mail_server = self.browse(cr, SUPERUSER_ID, mail_server_ids[0]) if mail_server: smtp_server = mail_server.smtp_host smtp_user = mail_server.smtp_user smtp_password = mail_server.smtp_pass smtp_port = mail_server.smtp_port smtp_encryption = mail_server.smtp_encryption smtp_debug = smtp_debug or mail_server.smtp_debug else: # we were passed an explicit smtp_server or nothing at all smtp_server = smtp_server or tools.config.get('smtp_server') smtp_port = tools.config.get( 'smtp_port', 25) if smtp_port is None else smtp_port smtp_user = smtp_user or tools.config.get('smtp_user') smtp_password = smtp_password or tools.config.get('smtp_password') if smtp_encryption is None and tools.config.get('smtp_ssl'): smtp_encryption = 'starttls' # STARTTLS is the new meaning of the smtp_ssl flag as of v7.0 #NG: Only Save Bcc in smtp_to_list if message.get('Bcc', False): del message['Bcc'] if not smtp_server: raise osv.except_osv( _("Missing SMTP Server"), _("Please define at least one SMTP server, or provide the SMTP parameters explicitly." )) try: message_id = message['Message-Id'] # Add email in Maildir if smtp_server contains maildir. if smtp_server.startswith('maildir:/'): from mailbox import Maildir maildir_path = smtp_server[8:] mdir = Maildir(maildir_path, factory=None, create=True) mdir.add(message.as_string(True)) return message_id try: smtp = self.connect(smtp_server, smtp_port, smtp_user, smtp_password, smtp_encryption or False, smtp_debug) smtp.sendmail(smtp_from, smtp_to_list, message.as_string()) finally: try: # Close Connection of SMTP Server smtp.quit() except Exception: # ignored, just a consequence of the previous exception pass except Exception, e: msg = _("Mail delivery failed via SMTP server '%s'.\n%s: %s") % ( tools.ustr(smtp_server), e.__class__.__name__, tools.ustr(e)) _logger.exception(msg) raise MailDeliveryException(_("Mail Delivery Failed"), msg)
def send(self, cr, uid, ids, auto_commit=False, raise_exception=False, context=None): """ 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 """ context = dict(context or {}) ir_mail_server = self.pool.get('ir.mail_server') ir_attachment = self.pool['ir.attachment'] ir_values = self.pool.get('ir.values') default_mail_server = ir_values.get_default(cr, uid, 'mail.mail', 'mail_server_id') #default_mail_address = ir_values.get_default(cr, uid, 'mail.mail', 'mail_server_address') ###### existing_ir_mail_server = self.pool.get('ir.mail_server').search( cr, uid, [('id', '=', default_mail_server)]) if existing_ir_mail_server != []: address = self.pool.get('ir.mail_server').browse( cr, uid, default_mail_server, context=context) default_mail_address = getattr(address, "smtp_user", False) else: address = False default_mail_address = False ######## 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) user = context.get('user_id', SUPERUSER_ID) if user != SUPERUSER_ID: mail_server = ir_mail_server.search( cr, uid, [('user_id', '=', user)], context=context) mail_server_obj = ir_mail_server.browse( cr, uid, mail_server) values = { 'email_from': mail_server_obj.user_id.login, 'reply_to': mail_server_obj.user_id.login } self.write(cr, uid, ids, values) else: partner_id = mail.author_id.id res_users_pool = self.pool.get('res.users') res_users_id = res_users_pool.search( cr, uid, [('partner_id', '=', partner_id)], context=context) mail_server = ir_mail_server.search( cr, uid, [('user_id', '=', res_users_id)], context=context) ### Ü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 for email in email_list: if not mail_server 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'), 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( cr, uid, msg, mail_server_id=default_mail_server, context=context) 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( cr, uid, msg, mail_server_id=mail_server[0], context=context) 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
def send(self, cr, uid, ids, auto_commit=False, raise_exception=False, context=None): """ 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.pool.get('ir.mail_server') ir_attachment = self.pool['ir.attachment'] for mail in self.browse(cr, SUPERUSER_ID, ids, context=context): try: # 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) # build an RFC2822 email.message.Message object and send it without queuing res = None for email in email_list: email_headers = dict(headers) if email.get('headers'): email_headers.update(email['headers']) 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.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=email_headers) res = ir_mail_server.send_email( cr, uid, msg, mail_server_id=mail.mail_server_id.id, context=context) if res: mail.write({'state': 'sent', 'message_id': res}) mail_sent = True else: mail.write({'state': 'exception'}) mail_sent = False # /!\ can't use mail.state here, as mail.refresh() will cause an error # see revid:[email protected] in 6.1 self._postprocess_sent_message(cr, uid, mail, context=context, mail_sent=mail_sent) _logger.info( 'Mail with ID %r and Message-Id %r successfully sent', mail.id, mail.message_id) 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
def send_email(self, cr, uid, message, mail_server_id=None, smtp_server=None, smtp_port=None, smtp_user=None, smtp_password=None, smtp_encryption=None, smtp_debug=False, context=None): """Override the standard method to fix the issue of using a mail client where relaying is disallowed.""" # Use the default bounce address **only if** no Return-Path was # provided by caller. Caller may be using Variable Envelope Return # Path (VERP) to detect no-longer valid email addresses. smtp_from = message['Return-Path'] if not smtp_from: smtp_from = self._get_default_bounce_address(cr, uid, context=context) if not smtp_from: smtp_from = message['From'] assert smtp_from, ( "The Return-Path or From header is required for any outbound email" ) # The email's "Envelope From" (Return-Path), and all recipient # addresses must only contain ASCII characters. from_rfc2822 = extract_rfc2822_addresses(smtp_from) assert from_rfc2822, ( "Malformed 'Return-Path' or 'From' address: %r - " "It should contain one valid plain ASCII email") % smtp_from # use last extracted email, to support rarities like # 'Support@MyComp <*****@*****.**>' smtp_from = from_rfc2822[-1] email_to = message['To'] email_cc = message['Cc'] email_bcc = message['Bcc'] smtp_to_list = filter( None, tools.flatten( map(extract_rfc2822_addresses, [email_to, email_cc, email_bcc]))) assert smtp_to_list, self.NO_VALID_RECIPIENT x_forge_to = message['X-Forge-To'] if x_forge_to: # `To:` header forged, e.g. for posting on mail.groups, # to avoid confusion del message['X-Forge-To'] del message['To'] # avoid multiple To: headers! message['To'] = x_forge_to # Do not actually send emails in testing mode! if getattr(threading.currentThread(), 'testing', False): _test_logger.info("skip sending email in test mode") return message['Message-Id'] # Get SMTP Server Details from Mail Server mail_server = None if mail_server_id: mail_server = self.browse(cr, SUPERUSER_ID, mail_server_id) elif not smtp_server: mail_server_ids = self.search(cr, SUPERUSER_ID, [], order='sequence', limit=1) if mail_server_ids: mail_server = self.browse(cr, SUPERUSER_ID, mail_server_ids[0]) if mail_server: smtp_server = mail_server.smtp_host smtp_user = mail_server.smtp_user smtp_password = mail_server.smtp_pass smtp_port = mail_server.smtp_port smtp_encryption = mail_server.smtp_encryption smtp_debug = smtp_debug or mail_server.smtp_debug else: # we were passed an explicit smtp_server or nothing at all smtp_server = smtp_server or tools.config.get('smtp_server') smtp_port = tools.config.get( 'smtp_port', 25) if smtp_port is None else smtp_port smtp_user = smtp_user or tools.config.get('smtp_user') smtp_password = smtp_password or tools.config.get('smtp_password') if smtp_encryption is None and tools.config.get('smtp_ssl'): smtp_encryption = 'starttls' # STARTTLS is the new meaning of the smtp_ssl flag as of v7.0 if not smtp_server: raise osv.except_osv( _("Missing SMTP Server"), _("Please define at least one SMTP server, or " "provide the SMTP parameters explicitly.")) try: message_id = message['Message-Id'] # Add email in Maildir if smtp_server contains maildir. if smtp_server.startswith('maildir:/'): from mailbox import Maildir maildir_path = smtp_server[8:] mdir = Maildir(maildir_path, factory=None, create=True) mdir.add(message.as_string(True)) return message_id smtp = None try: # START OF CODE ADDED smtp = self.connect(smtp_server, smtp_port, smtp_user, smtp_password, smtp_encryption or False, smtp_debug) # smtp.sendmail(smtp_from, smtp_to_list, message.as_string()) from email.utils import parseaddr, formataddr # exact name and address (oldname, oldemail) = parseaddr(message['From']) # use original name with new address newfrom = formataddr((oldname, smtp_user)) # need to use replace_header instead '=' to prevent # double field message.replace_header('From', newfrom) smtp.sendmail(smtp_user, smtp_to_list, message.as_string()) # END OF CODE ADDED finally: if smtp is not None: smtp.quit() except Exception, e: msg = _("Mail delivery failed via SMTP server '%s'.\n%s: %s") % ( tools.ustr(smtp_server), e.__class__.__name__, tools.ustr(e)) _logger.error(msg) raise MailDeliveryException(_("Mail Delivery Failed"), msg)
def send(self, cr, uid, ids, bcc=False, auto_commit=False, raise_exception=False, context=None): 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, 1, ids, context=context): try: if mail.model: model_id = self.pool['ir.model'].search(cr, 1, [('model', '=', mail.model)], context=context)[0] model = self.pool['ir.model'].browse(cr, 1, model_id, context=context) else: model = None if model: context['model_name'] = model.name 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, 1, attachment_ids, ['datas_fname', 'datas'])] 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 mail.write({'state': 'exception'}) mail_sent = False res = None for email in email_list: 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), email_bcc=tools.email_split(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 = 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: _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 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: _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): value = '. '.join(e.args) raise MailDeliveryException(_("Mail Delivery Failed"), value) raise if auto_commit is True: cr.commit() return True