def action_invite(self): """ Process the wizard content and proceed with sending the related email(s), rendering any template patterns on the fly if needed """ self.ensure_one() Partner = self.env['res.partner'] # compute partners and emails, try to find partners for given emails valid_partners = self.partner_ids valid_emails = [] for email in emails_split.split(self.emails or ''): partner = False email_normalized = tools.email_normalize(email) if email_normalized: partner = Partner.search([('email_normalized', '=', email_normalized)]) if partner: valid_partners |= partner else: email_formatted = tools.email_split_and_format(email) if email_formatted: valid_emails.extend(email_formatted) if not valid_partners and not valid_emails: raise UserError(_("Please enter at least one valid recipient.")) answers = self._prepare_answers(valid_partners, valid_emails) for answer in answers: self._send_mail(answer) return {'type': 'ir.actions.act_window_close'}
def _message_get_suggested_recipients(self): recipients = super(MailCCMixin, self)._message_get_suggested_recipients() for record in self: if record.email_cc: for email in tools.email_split_and_format(record.email_cc): record._message_add_suggested_recipient(recipients, email=email, reason=_('CC Email')) return recipients
def message_new(self, msg_dict, custom_values=None): if custom_values is None: custom_values = {} if custom_values.get('category_id', False): domain = [('category_id', '=', custom_values.pop('category_id'))] custom_values.pop else: domain = [] # find or create customer email = email_split(msg_dict.get('email_from', False))[0] name = email_split_and_format(msg_dict.get('email_from', False))[0] customer = self.env['nursery.customer'].find_or_create(email, name) # happy Xmas plants = self.env['nursery.plant'].search(domain) plant = self.env['nursery.plant'].browse([random.choice(plants.ids)]) custom_values.update({ 'customer_id': customer.id, 'line_ids': [(4, plant.id)], }) return super(Order, self).message_new(msg_dict, custom_values=custom_values)
def build_email(self, email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False, attachments=None, message_id=None, references=None, object_id=False, subtype='plain', headers=None, body_alternative=None, subtype_alternative='plain'): if tools.config.get('email_to'): email_to = tools.email_split_and_format(tools.config['email_to']) email_cc = None email_bcc = None msg = super(IrMailServer, self).build_email(email_from, email_to, subject, body, email_cc, email_bcc, reply_to, attachments, message_id, references, object_id, subtype, headers, body_alternative, subtype_alternative) return msg
def message_new(self, msg_dict, custom_values=None): if custom_values is None: custom_values = {} # find or create customer email_address = email_split(msg_dict.get('email_from', False))[0] customer = self.env['plant.customer'].search( [('email', 'ilike', email_address)], limit=1) if not customer: customer = self.env['plant.customer'].create({ 'name': email_split_and_format(msg_dict.get('email_from', False))[0], 'email': email_address }) # happy Xmas plants = self.env['plant.plant'].search([]) plant = self.env['plant.plant'].browse([random.choice(plants.ids)]) custom_values.update({ 'customer_id': customer.id, 'line_ids': [(4, plant.id)], }) return super(Order, self).message_new(msg_dict, custom_values=custom_values)
def _add_extra_recipients_suggestions(self, suggestions, field_mail, reason): ResPartnerObj = self.env['res.partner'] aliases = self.env['mail.alias'].get_aliases() email_extra_formated_list = [] for record in self: emails_extra = record.message_ids.mapped(field_mail) for email in emails_extra: email_extra_formated_list.extend(email_split_and_format(email)) email_extra_formated_list = set(email_extra_formated_list) email_extra_list = [ x[1] for x in getaddresses(email_extra_formated_list)] partners_info = self.message_partner_info_from_emails( email_extra_list) for pinfo in partners_info: partner_id = pinfo['partner_id'] email = pinfo['full_name'] if not partner_id: if email not in aliases: self._message_add_suggested_recipient( suggestions, email=email, reason=reason) else: partner = ResPartnerObj.browse(partner_id, self._prefetch) self._message_add_suggested_recipient( suggestions, partner=partner, reason=reason)
def action_invite(self): """ Process the wizard content and proceed with sending the related email(s), rendering any template patterns on the fly if needed """ self.ensure_one() Partner = self.env['res.partner'] # compute partners and emails, try to find partners for given emails valid_partners = self.partner_ids langs = set(valid_partners.mapped('lang')) - {False} if len(langs) == 1: self = self.with_context(lang=langs.pop()) valid_emails = [] for email in emails_split.split(self.emails or ''): partner = False email_normalized = tools.email_normalize(email) if email_normalized: limit = None if self.survey_users_login_required else 1 partner = Partner.search( [('email_normalized', '=', email_normalized)], limit=limit) if partner: valid_partners |= partner else: email_formatted = tools.email_split_and_format(email) if email_formatted: valid_emails.extend(email_formatted) if not valid_partners and not valid_emails: raise UserError(_("Please enter at least one valid recipient.")) answers = self._prepare_answers(valid_partners, valid_emails) for answer in answers: self._send_mail(answer) return {'type': 'ir.actions.act_window_close'}
def message_get_suggested_recipients(self): """Adds email Cc recipients as suggested recipients. If the recipient has a res.partner, use it. """ res = super().message_get_suggested_recipients() ResPartnerObj = self.env['res.partner'] email_cc_formated_list = [] for record in self: emails_cc = record.message_ids.mapped('email_cc') for email in emails_cc: email_cc_formated_list.extend(email_split_and_format(email)) email_cc_formated_list = set(email_cc_formated_list) for cc in email_cc_formated_list: email_parts = getaddresses([cc])[0] partner_id = record.message_partner_info_from_emails( [email_parts[1]])[0].get('partner_id') if not partner_id: record._message_add_suggested_recipient( res, email=cc, reason=_('Cc')) else: partner = ResPartnerObj.browse(partner_id, self._prefetch) record._message_add_suggested_recipient( res, partner=partner, reason=_('Cc')) return res
def _send_wecom_prepare_values(self, partner=None): """ 根据合作伙伴返回有关特定电子邮件值的字典,或者对整个邮件都是通用的。对于特定电子邮件值取决于对伙伴的字典,或者对mail.email_to给出的整个收件人来说都是通用的。 :param Model partner: 具体的收件人合作伙伴 """ self.ensure_one() body_html = self._send_prepare_body_html() body_json = self._send_prepare_body_json() body_markdown = self._send_prepare_body_markdown() # body_alternative = tools.html2plaintext(body_html) if partner: email_to = [ tools.formataddr((partner.name or "False", partner.email or "False")) ] message_to_user = [ tools.formataddr( (partner.name or "False", partner.wecom_userid or "False") ) ] else: email_to = tools.email_split_and_format(self.email_to) message_to_user = self.message_to_user res = { # "message_body_text": message_body_text, # "message_body_html": message_body_html, "email_to": email_to, "message_to_user": message_to_user, "body_json": body_json, "body_html": body_html, "body_markdown": body_markdown, } return res
def test_message_cc_update_no_old(self): record = self.env['mail.test.cc'].create({}) self.alias.write({'alias_force_thread_id': record.id}) self.format_and_process(MAIL_TEMPLATE, self.email_from, '*****@*****.**', cc='cc2 <*****@*****.**>, [email protected]', target_model='mail.test.cc') cc = email_split_and_format(record.email_cc) self.assertEqual(sorted(cc), ['cc2 <*****@*****.**>', '*****@*****.**'], 'new cc should have been added on record (unique)')
def test_message_cc_new(self): record = self.format_and_process(MAIL_TEMPLATE, self.email_from, '*****@*****.**', cc='[email protected], [email protected]', target_model='mail.test.cc') cc = email_split_and_format(record.email_cc) self.assertEqual(sorted(cc), ['*****@*****.**', '*****@*****.**'])
def send_get_mail_to(self, partner=None): """Forge the email_to with the following heuristic: - if 'partner', recipient specific (Partner Name <email>) - else fallback on mail.email_to splitting """ self.ensure_one() if partner: email_to = [formataddr((partner.name, partner.email))] else: email_to = tools.email_split_and_format(self.email_to) return email_to
def test_message_cc_new(self): alias = self.env['mail.alias'].create({ 'alias_name': 'cc_record', 'alias_user_id': False, 'alias_model_id': self.env['ir.model']._get('mail.test.cc').id, 'alias_contact': 'everyone'}) record = self.format_and_process(MAIL_TEMPLATE, target_model='mail.test.cc', to='*****@*****.**', cc='[email protected], [email protected]') cc = email_split_and_format(record.email_cc) self.assertEqual(sorted(cc), ['*****@*****.**', '*****@*****.**'], 'cc should contains exactly 2 cc')
def _get_recipients_from_record(self, form): """ Get's all mail recipients from res.partner and formio.mail.recipient. :return array: With mail recipients in a dictionary. """ res = [] for line in self.mail_recipient_line: for record in line.mail_recipients_partner_id: mail_values = {} mail = tools.email_split_and_format(record.email) if mail: mail_values['recipient'] = mail[0] if record.lang: mail_values['lang'] = record.lang mail_values['template'] = line.mail_template_id.id mail_values['report'] = line.mail_report_id.id res.append(mail_values) for record in line.mail_recipients_address_id: mail_values = {} mail = tools.email_split_and_format(record.email) if mail: mail_values['recipient'] = mail[0] mail_values['template'] = line.mail_template_id.id mail_values['report'] = line.mail_report_id.id res.append(mail_values) for record in line.mail_recipients_formio_component_id: mail_values = {} component_values = [] if record.key not in form._formio.input_components.keys(): continue obj = form._formio.input_components[record.key] component_values.extend(self._get_component_mail(obj)) for value in component_values: mail = tools.email_split_and_format(value) if mail: mail_values['recipient'] = mail[0] mail_values['template'] = line.mail_template_id.id mail_values['report'] = line.mail_report_id.id res.append(mail_values) return res
def test_message_cc_update_no_old(self): record = self.env['mail.test.cc'].create({}) alias = self.env['mail.alias'].create({ 'alias_name': 'cc_record', 'alias_user_id': False, 'alias_model_id': self.env['ir.model']._get('mail.test.cc').id, 'alias_contact': 'everyone', 'alias_force_thread_id': record.id}) self.format_and_process(MAIL_TEMPLATE, subject='Re: Frogs', target_model='mail.test.cc', msg_id='*****@*****.**', to='*****@*****.**', cc='cc2 <*****@*****.**>, [email protected]') cc = email_split_and_format(record.email_cc) self.assertEqual(sorted(cc), ['cc2 <*****@*****.**>', '*****@*****.**'], 'new cc should have been added on record (unique)')
def _track_sendgrid_emails(self): """ Create tracking e-mails after successfully sent with Sendgrid. """ self.ensure_one() m_tracking = self.env['mail.tracking.email'].sudo() track_vals = self._prepare_sendgrid_tracking() for recipient in tools.email_split_and_format(self.email_to): track_vals['recipient'] = recipient m_tracking += m_tracking.create(track_vals) for partner in self.recipient_ids: track_vals.update({ 'partner_id': partner.id, 'recipient': partner.email, }) m_tracking += m_tracking.create(track_vals) return m_tracking
def _onchange_emails(self): if self.emails and (self.survey_users_login_required and not self.survey_id.users_can_signup): raise UserError(_('This survey does not allow external people to participate. You should create user accounts or update survey access mode accordingly.')) if not self.emails: return valid, error = [], [] emails = list(set(emails_split.split(self.emails or ""))) for email in emails: email_check = tools.email_split_and_format(email) if not email_check: error.append(email) else: valid.extend(email_check) if error: raise UserError(_("Some emails you just entered are incorrect: %s") % (', '.join(error))) self.emails = '\n'.join(valid)
def find_or_create(self, email): """ Find a partner with the given ``email`` or use :py:method:`~.name_create` to create one :param str email: email-like string, which should contain at least one email, e.g. ``"Raoul Grosbedon <*****@*****.**>"``""" assert email, 'an email is required for find_or_create to work' emails = tools.email_split(email) name_emails = tools.email_split_and_format(email) if emails: email = emails[0] name_email = name_emails[0] else: name_email = email partners = self.search([('email', '=ilike', email)], limit=1) return partners.id or self.name_create(name_email)[0]
def _get_recipients_from_component(self): """ Computes all formio.components specified in the mail_recipients_formio_component_ids field. :return array: With mail recipients in a dictionary. """ values = [] result = [] components = self.builder_id.mail_recipients_formio_component_ids for comp in components: if comp.key not in self._formio.input_components.keys(): continue comp_obj = self._formio.input_components[comp.key] values.extend(self.builder_id._get_component_mail(comp_obj)) for v in values: mail = tools.email_split_and_format(v) if mail: result.append({'recipient': mail[0]}) return result
def _send_prepare_values(self, partner=None): """Return a dictionary for specific email values, depending on a partner, or generic to the whole recipients given by mail.email_to. :param Model partner: specific recipient partner """ self.ensure_one() body = self._send_prepare_body() body_alternative = tools.html2plaintext(body) if partner: email_to = [formataddr((partner.name or 'False', partner.email or 'False'))] else: email_to = tools.email_split_and_format(self.email_to) res = { 'body': body, 'body_alternative': body_alternative, 'email_to': email_to, } return res
def message_get_suggested_recipients(self): res = super().message_get_suggested_recipients() ResPartnerObj = self.env['res.partner'] for record in self: messages = record.message_ids.filtered('email_cc') for msg in messages: email_cc_list = email_split_and_format(msg.email_cc) for cc in email_cc_list: email_parts = getaddresses([cc])[0] partner_id = record.message_partner_info_from_emails( [email_parts[1]])[0].get('partner_id') if not partner_id: res[record.id].append((False, cc, _('Cc'))) else: partner = ResPartnerObj.browse(partner_id, self._prefetch) record._message_add_suggested_recipient( res, partner=partner, reason=_('Cc')) return res
def message_parse(self, message, save_original=False): """ 解析表示RFC-2822电子邮件的 email.message.message,并返回包含消息详细信息的通用dict。 :param message: email to parse :type message: email.message.Message :param bool save_original: whether the returned dict should include an ``original`` attachment containing the source of the message :rtype: dict :return: A dict with the following structure, where each field may not be present if missing in original message:: { 'message_id': msg_id, 'subject': subject, 'email_from': from, 'to': to + delivered-to, 'cc': cc, 'recipients': delivered-to + to + cc + resent-to + resent-cc, 'partner_ids': partners found based on recipients emails, 'body': unified_body, 'references': references, 'in_reply_to': in-reply-to, 'parent_id': parent mail.message based on in_reply_to or references, 'is_internal': answer to an internal message (note), 'date': date, 'attachments': [('file1', 'bytes'), ('file2', 'bytes')} """ if not isinstance(message, EmailMessage): raise ValueError( _("Message should be a valid EmailMessage instance")) msg_dict = {"message_type": "email"} message_id = message.get("Message-Id") if not message_id: # 非常不寻常的情况,就是我们在这里应该容错 message_id = "<%s@localhost>" % time.time() _logger.debug( "Parsing Message without message-id, generating a random one: %s", message_id, ) msg_dict["message_id"] = message_id.strip() if message.get("Subject"): msg_dict["subject"] = tools.decode_message_header( message, "Subject") email_from = tools.decode_message_header(message, "From") email_cc = tools.decode_message_header(message, "cc") email_from_list = tools.email_split_and_format(email_from) email_cc_list = tools.email_split_and_format(email_cc) msg_dict["email_from"] = email_from_list[ 0] if email_from_list else email_from msg_dict["from"] = msg_dict[ "email_from"] # compatibility for message_new msg_dict["cc"] = ",".join(email_cc_list) if email_cc_list else email_cc # Delivered-To is a safe bet in most modern MTAs, but we have to fallback on To + Cc values # for all the odd MTAs out there, as there is no standard header for the envelope's `rcpt_to` value. msg_dict["recipients"] = ",".join( set(formatted_email for address in [ tools.decode_message_header(message, "Delivered-To"), tools.decode_message_header(message, "To"), tools.decode_message_header(message, "Cc"), tools.decode_message_header(message, "Resent-To"), tools.decode_message_header(message, "Resent-Cc"), ] if address for formatted_email in tools.email_split_and_format(address))) msg_dict["to"] = ",".join( set(formatted_email for address in [ tools.decode_message_header(message, "Delivered-To"), tools.decode_message_header(message, "To"), ] if address for formatted_email in tools.email_split_and_format(address))) partner_ids = [ x.id for x in self._mail_find_partner_from_emails( tools.email_split(msg_dict["recipients"]), records=self) if x ] msg_dict["partner_ids"] = partner_ids # compute references to find if email_message is a reply to an existing thread msg_dict["references"] = tools.decode_message_header( message, "References") msg_dict["in_reply_to"] = tools.decode_message_header( message, "In-Reply-To").strip() if message.get("Date"): try: date_hdr = tools.decode_message_header(message, "Date") parsed_date = dateutil.parser.parse(date_hdr, fuzzy=True) if parsed_date.utcoffset() is None: # naive datetime, so we arbitrarily decide to make it # UTC, there's no better choice. Should not happen, # as RFC2822 requires timezone offset in Date headers. stored_date = parsed_date.replace(tzinfo=pytz.utc) else: stored_date = parsed_date.astimezone(tz=pytz.utc) except Exception: _logger.info( "Failed to parse Date header %r in incoming mail " "with message-id %r, assuming current date/time.", message.get("Date"), message_id, ) stored_date = datetime.datetime.now() msg_dict["date"] = stored_date.strftime( tools.DEFAULT_SERVER_DATETIME_FORMAT) parent_ids = False if msg_dict["in_reply_to"]: parent_ids = self.env["mail.message"].search( [("message_id", "=", msg_dict["in_reply_to"])], limit=1) if msg_dict["references"] and not parent_ids: references_msg_id_list = tools.mail_header_msgid_re.findall( msg_dict["references"]) parent_ids = self.env["mail.message"].search( [("message_id", "in", [x.strip() for x in references_msg_id_list])], limit=1, ) if parent_ids: msg_dict["parent_id"] = parent_ids.id msg_dict["is_internal"] = (parent_ids.subtype_id and parent_ids.subtype_id.internal or False) msg_dict.update( self._message_parse_extract_payload(message, save_original=save_original)) msg_dict.update(self._message_parse_extract_bounce(message, msg_dict)) return msg_dict
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, auto_commit=False, raise_exception=False, smtp_session=None): IrMailServer = self.env['ir.mail_server'] IrAttachment = self.env['ir.attachment'] for mail_id in self.ids: success_pids = [] failure_type = None processing_pid = None mail = None try: mail = self.browse(mail_id) if mail.state != 'outgoing': if mail.state != 'exception' and mail.auto_delete: mail.sudo().unlink() continue # remove attachments if user send the link with the access_token body = mail.body_html or '' attachments = mail.attachment_ids for link in re.findall(r'/web/(?:content|image)/([0-9]+)', body): attachments = attachments - IrAttachment.browse(int(link)) # 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 attachments.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_prepare_values()) for partner in mail.recipient_ids: values = mail._send_prepare_values(partner=partner) values['partner_id'] = partner email_list.append(values) 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.' ), }) # 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_id', 'in', mail.ids), ('email_status', 'not in', ('sent', 'canceled')) ]) if notifs: notif_msg = _( 'Error without exception. Probably due do concurrent access update of notification records. Please see with an administrator.' ) notifs.write({ 'email_status': 'exception', 'failure_type': 'UNKNOWN', 'failure_reason': notif_msg, }) # 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) processing_pid = email.pop("partner_id", None) try: res = IrMailServer.send_email( msg, mail_server_id=mail.mail_server_id.id, smtp_session=smtp_session) if processing_pid: success_pids.append(processing_pid) processing_pid = None except AssertionError as error: if str(error) == IrMailServer.NO_VALID_RECIPIENT: failure_type = "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 has been sent at least once, no major exception occured mail.write({ 'state': 'sent', 'message_id': res, 'failure_reason': False }) _logger.info( 'Mail with ID %r and Message-Id %r successfully sent', mail.id, mail.message_id) # /!\ can't use mail.state here, as mail.refresh() will cause an error # see revid:[email protected] in 6.1 mail._postprocess_sent_message(success_pids=success_pids, failure_type=failure_type) 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) # mail status will stay on ongoing since transaction will be rollback 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(success_pids=success_pids, failure_reason=failure_reason, failure_type='UNKNOWN') if raise_exception: if isinstance(e, (AssertionError, UnicodeEncodeError)): if isinstance(e, UnicodeEncodeError): value = "Invalid text: %s" % e.object else: # 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