def generate_recipients(self, results, res_ids): """Generates the recipients of the template. Default values can ben generated instead of the template values if requested by template or context. Emails (email_to, email_cc) can be transformed into partners if requested in the context. """ self.ensure_one() if self.use_default_to or self._context.get('tpl_force_default_to'): default_recipients = self.env[ 'mail.thread'].message_get_default_recipients( res_model=self.model, res_ids=res_ids) for res_id, recipients in default_recipients.items(): results[res_id].pop('partner_to', None) results[res_id].update(recipients) for res_id, values in results.items(): partner_ids = values.get('partner_ids', list()) if self._context.get('tpl_partners_only'): mails = tools.email_split(values.pop( 'email_to', '')) + tools.email_split( values.pop('email_cc', '')) for mail in mails: partner_id = self.env['res.partner'].find_or_create(mail) partner_ids.append(partner_id) partner_to = values.pop('partner_to', '') if partner_to: # placeholders could generate '', 3, 2 due to some empty field values tpl_partner_ids = [ int(pid) for pid in partner_to.split(',') if pid ] partner_ids += self.env['res.partner'].sudo().browse( tpl_partner_ids).exists().ids results[res_id]['partner_ids'] = partner_ids return results
def generate_recipients(self, results, res_ids): """Generates the recipients of the template. Default values can ben generated instead of the template values if requested by template or context. Emails (email_to, email_cc) can be transformed into partners if requested in the context. """ self.ensure_one() if self.use_default_to or self._context.get('tpl_force_default_to'): records = self.env[self.model].browse(res_ids).sudo() default_recipients = records._message_get_default_recipients() for res_id, recipients in default_recipients.items(): results[res_id].pop('partner_to', None) results[res_id].update(recipients) records_company = None if self._context.get( 'tpl_partners_only' ) and self.model and results and 'company_id' in self.env[ self.model]._fields: records = self.env[self.model].browse(results.keys()).read( ['company_id']) records_company = { rec['id']: (rec['company_id'][0] if rec['company_id'] else None) for rec in records } for res_id, values in results.items(): partner_ids = values.get('partner_ids', list()) if self._context.get('tpl_partners_only'): mails = tools.email_split(values.pop( 'email_to', '')) + tools.email_split( values.pop('email_cc', '')) Partner = self.env['res.partner'] if records_company: Partner = Partner.with_context( default_company_id=records_company[res_id]) for mail in mails: partner = Partner.find_or_create(mail) partner_ids.append(partner.id) partner_to = values.pop('partner_to', '') if partner_to: # placeholders could generate '', 3, 2 due to some empty field values tpl_partner_ids = [ int(pid) for pid in partner_to.split(',') if pid ] partner_ids += self.env['res.partner'].sudo().browse( tpl_partner_ids).exists().ids results[res_id]['partner_ids'] = partner_ids return results
def message_new(self, msg, custom_values=None): """ Overrides mail_thread message_new that is called by the mailgateway through message_process. This override updates the document according to the email. """ if custom_values is None: custom_values = {} email = tools.email_split(msg.get('from')) and tools.email_split(msg.get('from'))[0] or False user = self.env['res.users'].search([('login', '=', email)], limit=1) if user: employee = self.env['hr.employee'].search([('user_id', '=', user.id)], limit=1) if employee: custom_values['employee_id'] = employee and employee[0].id return super(MaintenanceRequest, self).message_new(msg, custom_values=custom_values)
def message_route(self, message, message_dict, model=None, thread_id=None, custom_values=None): """ Override to udpate mass mailing statistics based on bounce emails """ bounce_alias = self.env['ir.config_parameter'].sudo().get_param( "mail.bounce.alias") email_to = decode_message_header(message, 'To') email_to_localpart = (tools.email_split(email_to) or [''])[0].split('@', 1)[0].lower() if bounce_alias and bounce_alias in email_to_localpart: bounce_re = re.compile( "%s\+(\d+)-?([\w.]+)?-?(\d+)?" % re.escape(bounce_alias), re.UNICODE) bounce_match = bounce_re.search(email_to) if bounce_match: bounced_mail_id = bounce_match.group(1) self.env['mail.mail.statistics'].set_bounced( mail_mail_ids=[bounced_mail_id]) return super(MailThread, self).message_route(message, message_dict, model, thread_id, custom_values)
def _parse_partner_name(self, text): """ Parse partner name (given by text) in order to find a name and an email. Supported syntax: * Raoul <*****@*****.**> * "Raoul le Grand" <*****@*****.**> * Raoul [email protected] (strange fault tolerant support from df40926d2a57c101a3e2d221ecfd08fbb4fea30e) Otherwise: default, everything is set as the name. Starting from 13.3 returned email will be normalized to have a coherent encoding. """ name, email = '', '' split_results = tools.email_split_tuples(text) if split_results: name, email = split_results[0] if email and not name: fallback_emails = tools.email_split(text.replace(' ', ',')) if fallback_emails: email = fallback_emails[0] name = text[:text.index(email)].replace('"', '').replace('<', '').strip() if email: email = tools.email_normalize(email) else: name, email = text, '' return name, email
def get_valid_email(self, msg): emails_list = [] valid_email = tools.email_split((msg.get('to') or '') + ',' + (msg.get('cc') or '')) team_aliases = self.mapped('team_id.alias_name') for eml in valid_email: if eml.split('@')[0] not in team_aliases: emails_list+= [eml] return emails_list
def message_new(self, msg_dict, custom_values=None): if custom_values is None: custom_values = {} email_address = email_split(msg_dict.get('email_from', False))[0] employee = self.env['hr.employee'].search([ '|', ('work_email', 'ilike', email_address), ('user_id.email', 'ilike', email_address) ], limit=1) expense_description = msg_dict.get('subject', '') # Match the first occurence of '[]' in the string and extract the content inside it # Example: '[foo] bar (baz)' becomes 'foo'. This is potentially the product code # of the product to encode on the expense. If not, take the default product instead # which is 'Fixed Cost' default_product = self.env.ref('hr_expense.product_product_fixed_cost') pattern = '\[([^)]*)\]' product_code = re.search(pattern, expense_description) if product_code is None: product = default_product else: expense_description = expense_description.replace(product_code.group(), '') products = self.env['product.product'].search([('default_code', 'ilike', product_code.group(1))]) or default_product product = products.filtered(lambda p: p.default_code == product_code.group(1)) or products[0] account = product.product_tmpl_id._get_product_accounts()['expense'] pattern = '[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?' # Match the last occurence of a float in the string # Example: '[foo] 50.3 bar 34.5' becomes '34.5'. This is potentially the price # to encode on the expense. If not, take 1.0 instead expense_price = re.findall(pattern, expense_description) # TODO: International formatting if not expense_price: price = 1.0 else: price = expense_price[-1][0] expense_description = expense_description.replace(price, '') try: price = float(price) except ValueError: price = 1.0 custom_values.update({ 'name': expense_description.strip(), 'employee_id': employee.id, 'product_id': product.id, 'product_uom_id': product.uom_id.id, 'tax_ids': [(4, tax.id, False) for tax in product.supplier_taxes_id], 'quantity': 1, 'unit_amount': price, 'company_id': employee.company_id.id, }) if account: custom_values['account_id'] = account.id return super(HrExpense, self).message_new(msg_dict, custom_values)
def test_email_split(self): cases = [ ("John <*****@*****.**>", ['*****@*****.**']), # regular form ("d@x; 1@2", ['d@x', '1@2']), # semi-colon + extra space ("'(ss)' <*****@*****.**>, 'foo' <foo@bar>", ['*****@*****.**', 'foo@bar']), # comma + single-quoting ('"*****@*****.**"<*****@*****.**>', ['*****@*****.**']), # double-quoting ('"<jg>" <*****@*****.**>', ['*****@*****.**']), # double-quoting with brackets ] for text, expected in cases: self.assertEqual(email_split(text), expected, 'email_split is broken')
def _parse_partner_name(self, text, context=None): """ Supported syntax: - 'Raoul <*****@*****.**>': will find name and email address - otherwise: default, everything is set as the name """ emails = tools.email_split(text.replace(' ', ',')) if emails: email = emails[0] name = text[:text.index(email)].replace('"', '').replace('<', '').strip() else: name, email = text, '' return name, email
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) if emails: email = emails[0] partners = self.search([('email', '=ilike', email)], limit=1) return partners.id or self.name_create(email)[0]
def _alias_check_contact(self, message, message_dict, alias): if alias.alias_contact == 'employees' and self.ids: email_from = tools.decode_message_header(message, 'From') email_address = tools.email_split(email_from)[0] employee = self.env['hr.employee'].search([('work_email', 'ilike', email_address)], limit=1) if not employee: employee = self.env['hr.employee'].search([('user_id.email', 'ilike', email_address)], limit=1) if not employee: return { 'error_message': 'restricted to employees', 'error_template': self.env.ref('hr.mail_template_data_unknown_employee_email_address').body_html, } return True return super(MailAlias, self)._alias_check_contact(message, message_dict, alias)
def send_get_email_dict(self, partner=None): # TDE: temporary addition (mail was parameter) due to semi-new-API res = super(MailMail, self).send_get_email_dict(partner) base_url = self.env['ir.config_parameter'].sudo().get_param( 'web.base.url') if self.mailing_id and res.get('body') and res.get('email_to'): emails = tools.email_split(res.get('email_to')[0]) email_to = emails and emails[0] or False unsubscribe_url = self._get_unsubscribe_url(email_to) link_to_replace = base_url + '/unsubscribe_from_list' if link_to_replace in res['body']: res['body'] = res['body'].replace( link_to_replace, unsubscribe_url if unsubscribe_url else '#') return res
def _alias_get_error_message(self, message, message_dict, alias): if alias.alias_contact == 'employees': email_from = tools.decode_message_header(message, 'From') email_address = tools.email_split(email_from)[0] employee = self.env['hr.employee'].search( [('work_email', 'ilike', email_address)], limit=1) if not employee: employee = self.env['hr.employee'].search( [('user_id.email', 'ilike', email_address)], limit=1) if not employee: return _('restricted to employees') return False return super(BaseModel, self)._alias_get_error_message(message, message_dict, alias)
def _send_prepare_values(self, partner=None): # TDE: temporary addition (mail was parameter) due to semi-new-API res = super(MailMail, self)._send_prepare_values(partner) base_url = self.env['ir.config_parameter'].sudo().get_param( 'web.base.url').rstrip('/') if self.mailing_id and res.get('body') and res.get('email_to'): emails = tools.email_split(res.get('email_to')[0]) email_to = emails and emails[0] or False urls_to_replace = [ (base_url + '/unsubscribe_from_list', self.mailing_id._get_unsubscribe_url(email_to, self.res_id)), (base_url + '/view', self.mailing_id._get_view_url(email_to, self.res_id)) ] for url_to_replace, new_url in urls_to_replace: if url_to_replace in res['body']: res['body'] = res['body'].replace( url_to_replace, new_url if new_url else '#') return res
def send_mail_test(self): self.ensure_one() mails = self.env['mail.mail'] mailing = self.mass_mailing_id test_emails = tools.email_split(self.email_to) for test_mail in test_emails: # Convert links in absolute URLs before the application of the shortener mailing.write({ 'body_html': self.env['mail.template']._replace_local_links( mailing.body_html) }) mail_values = { 'email_from': mailing.email_from, 'reply_to': mailing.reply_to, 'email_to': test_mail, 'subject': mailing.name, 'body_html': tools.html_sanitize(mailing.body_html, sanitize_attributes=True, sanitize_style=True, strip_classes=True), 'notification': True, 'mailing_id': mailing.id, 'attachment_ids': [(4, attachment.id) for attachment in mailing.attachment_ids], 'auto_delete': True, } mail = self.env['mail.mail'].create(mail_values) mails |= mail mails.send() 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['name'], base64.b64decode(a['datas']), a['mimetype']) for a in attachments.sudo().read( ['name', 'datas', 'mimetype']) if a['datas'] is not False] # 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) # 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.mail_message_id.is_thread_message(): 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(ast.literal_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([ ('notification_type', '=', 'email'), ('mail_id', 'in', mail.ids), ('notification_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.sudo().write({ 'notification_status': 'exception', 'failure_type': 'UNKNOWN', 'failure_reason': notif_msg, }) # `test_mail_bounce_during_send`, force immediate update to obtain the lock. # see rev. 56596e5240ef920df14d99087451ce6f06ac6d36 notifs.flush(fnames=[ 'notification_status', 'failure_type', 'failure_reason' ], records=notifs) # 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) 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, 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(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: value = '. '.join(e.args) raise MailDeliveryException(value) raise if auto_commit is True: self._cr.commit() return True
def send_mail_test(self): self.ensure_one() ctx = dict(self.env.context) ctx.pop('default_state', None) self = self.with_context(ctx) mails_sudo = self.env['mail.mail'].sudo() mailing = self.mass_mailing_id test_emails = tools.email_split(self.email_to) mass_mail_layout = self.env.ref( 'mass_mailing.mass_mailing_mail_layout') record = self.env[mailing.mailing_model_real].search([], limit=1) body = mailing._prepend_preview(mailing.body_html, mailing.preview) subject = mailing.subject # If there is atleast 1 record for the model used in this mailing, then we use this one to render the template # Downside: Jinja syntax is only tested when there is atleast one record of the mailing's model if record: # Returns a proper error if there is a syntax error with jinja body = self.env['mail.render.mixin']._render_template( body, mailing.mailing_model_real, record.ids, post_process=True)[record.id] subject = self.env['mail.render.mixin']._render_template( subject, mailing.mailing_model_real, record.ids)[record.id] # Convert links in absolute URLs before the application of the shortener body = self.env['mail.render.mixin']._replace_local_links(body) body = tools.html_sanitize(body, sanitize_attributes=True, sanitize_style=True) for test_mail in test_emails: mail_values = { 'email_from': mailing.email_from, 'reply_to': mailing.reply_to, 'email_to': test_mail, 'subject': subject, 'body_html': mass_mail_layout._render({'body': body}, engine='ir.qweb', minimal_qcontext=True), 'notification': True, 'mailing_id': mailing.id, 'attachment_ids': [(4, attachment.id) for attachment in mailing.attachment_ids], 'auto_delete': True, 'mail_server_id': mailing.mail_server_id.id, } mail = self.env['mail.mail'].sudo().create(mail_values) mails_sudo |= mail mails_sudo.send() return True
def generate_data(self, event, isCreating=False): if event.allday: start_date = event.start_date final_date = (datetime.strptime(event.stop_date, tools.DEFAULT_SERVER_DATE_FORMAT) + timedelta(days=1)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT) type = 'date' vstype = 'dateTime' else: start_date = fields.Datetime.context_timestamp(self, fields.Datetime.from_string(event.start)).isoformat('T') final_date = fields.Datetime.context_timestamp(self, fields.Datetime.from_string(event.stop)).isoformat('T') type = 'dateTime' vstype = 'date' attendee_list = [] for attendee in event.attendee_ids: email = tools.email_split(attendee.email) email = email[0] if email else '*****@*****.**' attendee_list.append({ 'email': email, 'displayName': attendee.partner_id.name, 'responseStatus': attendee.state or 'needsAction', }) reminders = [] for alarm in event.alarm_ids: reminders.append({ "method": "email" if alarm.type == "email" else "popup", "minutes": alarm.duration_minutes }) data = { "summary": event.name or '', "description": event.description or '', "start": { type: start_date, vstype: None, 'timeZone': self.env.context.get('tz') or 'UTC', }, "end": { type: final_date, vstype: None, 'timeZone': self.env.context.get('tz') or 'UTC', }, "attendees": attendee_list, "reminders": { "overrides": reminders, "useDefault": "false" }, "location": event.location or '', "visibility": event['privacy'] or 'public', } if event.recurrency and event.rrule: data["recurrence"] = ["RRULE:" + event.rrule] if not event.active: data["state"] = "cancelled" if not self.get_need_synchro_attendee(): data.pop("attendees") if isCreating: other_google_ids = [other_att.google_internal_event_id for other_att in event.attendee_ids if other_att.google_internal_event_id and not other_att.google_internal_event_id.startswith('_')] if other_google_ids: data["id"] = other_google_ids[0] return data
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 and \ mail.keep_days < 0: 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)) # 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: 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, 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 extract_email(email): """ extract the email address from a user-friendly email address """ addresses = email_split(email) return addresses[0] if addresses else ''
def get_mail_values(self, res_ids): """ Override method that generated the mail content by creating the mail.mail.statistics values in the o2m of mail_mail, when doing pure email mass mailing. """ self.ensure_one() res = super(MailComposeMessage, self).get_mail_values(res_ids) # use only for allowed models in mass mailing if self.composition_mode == 'mass_mail' and \ (self.mass_mailing_name or self.mass_mailing_id) and \ self.env['ir.model'].sudo().search([('model', '=', self.model), ('is_mail_thread', '=', True)], limit=1): mass_mailing = self.mass_mailing_id if not mass_mailing: reply_to_mode = 'email' if self.no_auto_thread else 'thread' reply_to = self.reply_to if self.no_auto_thread else False mass_mailing = self.env['mail.mass_mailing'].create({ 'mass_mailing_campaign_id': self.mass_mailing_campaign_id.id, 'name': self.mass_mailing_name, 'template_id': self.template_id.id, 'state': 'done', 'reply_to_mode': reply_to_mode, 'reply_to': reply_to, 'sent_date': fields.Datetime.now(), 'body_html': self.body, 'mailing_model_id': self.env['ir.model']._get(self.model).id, 'mailing_domain': self.active_domain, }) # Preprocess res.partners to batch-fetch from db # if recipient_ids is present, it means they are partners # (the only object to fill get_default_recipient this way) recipient_partners_ids = [] read_partners = {} for res_id in res_ids: mail_values = res[res_id] if mail_values.get('recipient_ids'): # recipient_ids is a list of x2m command tuples at this point recipient_partners_ids.append(mail_values.get('recipient_ids')[0][1]) read_partners = self.env['res.partner'].browse(recipient_partners_ids) partners_email = {p.id: p.email for p in read_partners} blacklist = self._context.get('mass_mailing_blacklist') seen_list = self._context.get('mass_mailing_seen_list') for res_id in res_ids: mail_values = res[res_id] if mail_values.get('email_to'): recips = tools.email_split(mail_values['email_to']) else: recips = tools.email_split(partners_email.get(res_id)) mail_to = recips[0].lower() if recips else False if (blacklist and mail_to in blacklist) or (seen_list and mail_to in seen_list): # prevent sending to blocked addresses that were included by mistake mail_values['state'] = 'cancel' elif seen_list is not None: seen_list.add(mail_to) stat_vals = { 'model': self.model, 'res_id': res_id, 'mass_mailing_id': mass_mailing.id } # propagate exception state to stat when still-born if mail_values.get('state') == 'cancel': stat_vals['exception'] = fields.Datetime.now() mail_values.update({ 'mailing_id': mass_mailing.id, 'statistics_ids': [(0, 0, stat_vals)], # email-mode: keep original message for routing 'notification': mass_mailing.reply_to_mode == 'thread', 'auto_delete': not mass_mailing.keep_archives, }) return res
def email_split(self, msg): email_list = tools.email_split((msg.get('to') or '') + ',' + (msg.get('cc') or '')) # check left-part is not already an alias aliases = self.mapped('project_id.alias_name') return [x for x in email_list if x.split('@')[0] not in aliases]