summaryrefslogtreecommitdiff
path: root/addons/mass_mailing/models/mass_mailing.py
diff options
context:
space:
mode:
Diffstat (limited to 'addons/mass_mailing/models/mass_mailing.py')
-rw-r--r--addons/mass_mailing/models/mass_mailing.py571
1 files changed, 571 insertions, 0 deletions
diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py
new file mode 100644
index 00000000000..bd314f03a3d
--- /dev/null
+++ b/addons/mass_mailing/models/mass_mailing.py
@@ -0,0 +1,571 @@
+# -*- coding: utf-8 -*-
+
+from datetime import datetime
+from dateutil import relativedelta
+import json
+import random
+import urllib
+import urlparse
+
+from openerp import tools
+from openerp.tools.safe_eval import safe_eval as eval
+from openerp.tools.translate import _
+from openerp.osv import osv, fields
+
+
+class MassMailingCategory(osv.Model):
+ """Model of categories of mass mailing, i.e. marketing, newsletter, ... """
+ _name = 'mail.mass_mailing.category'
+ _description = 'Mass Mailing Category'
+ _order = 'name'
+
+ _columns = {
+ 'name': fields.char('Name', required=True),
+ }
+
+
+class MassMailingContact(osv.Model):
+ """Model of a contact. This model is different from the partner model
+ because it holds only some basic information: name, email. The purpose is to
+ be able to deal with large contact list to email without bloating the partner
+ base."""
+ _name = 'mail.mass_mailing.contact'
+ _description = 'Mass Mailing Contact'
+ _order = 'email'
+ _rec_name = 'email'
+
+ _columns = {
+ 'name': fields.char('Name'),
+ 'email': fields.char('Email', required=True),
+ 'create_date': fields.datetime('Create Date'),
+ 'list_id': fields.many2one(
+ 'mail.mass_mailing.list', string='Mailing List',
+ ondelete='cascade', required=True,
+ ),
+ 'opt_out': fields.boolean('Opt Out', help='The contact has chosen not to receive mails anymore from this list'),
+ }
+
+ def _get_latest_list(self, cr, uid, context={}):
+ lid = self.pool.get('mail.mass_mailing.list').search(cr, uid, [], limit=1, order='id desc', context=context)
+ return lid and lid[0] or False
+
+ _defaults = {
+ 'list_id': _get_latest_list
+ }
+
+ def name_create(self, cr, uid, name, context=None):
+ name, email = self.pool['res.partner']._parse_partner_name(name, context=context)
+ if name and not email:
+ email = name
+ if email and not name:
+ name = email
+ rec_id = self.create(cr, uid, {'name': name, 'email': email}, context=context)
+ return self.name_get(cr, uid, [rec_id], context)[0]
+
+
+class MassMailingList(osv.Model):
+ """Model of a contact list. """
+ _name = 'mail.mass_mailing.list'
+ _order = 'name'
+ _description = 'Mailing List'
+
+ def _get_contact_nbr(self, cr, uid, ids, name, arg, context=None):
+ result = dict.fromkeys(ids, 0)
+ Contacts = self.pool.get('mail.mass_mailing.contact')
+ for group in Contacts.read_group(cr, uid, [('list_id', 'in', ids), ('opt_out', '!=', True)], ['list_id'], ['list_id'], context=context):
+ result[group['list_id'][0]] = group['list_id_count']
+ return result
+
+ _columns = {
+ 'name': fields.char('Mailing List', required=True),
+ 'contact_nbr': fields.function(
+ _get_contact_nbr, type='integer',
+ string='Number of Contacts',
+ ),
+ }
+
+
+class MassMailingStage(osv.Model):
+ """Stage for mass mailing campaigns. """
+ _name = 'mail.mass_mailing.stage'
+ _description = 'Mass Mailing Campaign Stage'
+ _order = 'sequence'
+
+ _columns = {
+ 'name': fields.char('Name', required=True, translate=True),
+ 'sequence': fields.integer('Sequence'),
+ }
+
+ _defaults = {
+ 'sequence': 0,
+ }
+
+
+class MassMailingCampaign(osv.Model):
+ """Model of mass mailing campaigns. """
+ _name = "mail.mass_mailing.campaign"
+ _description = 'Mass Mailing Campaign'
+
+ def _get_statistics(self, cr, uid, ids, name, arg, context=None):
+ """ Compute statistics of the mass mailing campaign """
+ Statistics = self.pool['mail.mail.statistics']
+ results = dict.fromkeys(ids, False)
+ for cid in ids:
+ stat_ids = Statistics.search(cr, uid, [('mass_mailing_campaign_id', '=', cid)], context=context)
+ stats = Statistics.browse(cr, uid, stat_ids, context=context)
+ results[cid] = {
+ 'total': len(stats),
+ 'failed': len([s for s in stats if not s.scheduled is False and s.sent is False and not s.exception is False]),
+ 'scheduled': len([s for s in stats if not s.scheduled is False and s.sent is False and s.exception is False]),
+ 'sent': len([s for s in stats if not s.sent is False]),
+ 'opened': len([s for s in stats if not s.opened is False]),
+ 'replied': len([s for s in stats if not s.replied is False]),
+ 'bounced': len([s for s in stats if not s.bounced is False]),
+ }
+ results[cid]['delivered'] = results[cid]['sent'] - results[cid]['bounced']
+ results[cid]['received_ratio'] = 100.0 * results[cid]['delivered'] / (results[cid]['total'] or 1)
+ results[cid]['opened_ratio'] = 100.0 * results[cid]['opened'] / (results[cid]['total'] or 1)
+ results[cid]['replied_ratio'] = 100.0 * results[cid]['replied'] / (results[cid]['total'] or 1)
+ return results
+
+ _columns = {
+ 'name': fields.char('Name', required=True),
+ 'stage_id': fields.many2one('mail.mass_mailing.stage', 'Stage', required=True),
+ 'user_id': fields.many2one(
+ 'res.users', 'Responsible',
+ required=True,
+ ),
+ 'category_ids': fields.many2many(
+ 'mail.mass_mailing.category', 'mail_mass_mailing_category_rel',
+ 'category_id', 'campaign_id', string='Categories'),
+ 'mass_mailing_ids': fields.one2many(
+ 'mail.mass_mailing', 'mass_mailing_campaign_id',
+ 'Mass Mailings',
+ ),
+ 'unique_ab_testing': fields.boolean(
+ 'AB Testing',
+ help='If checked, recipients will be mailed only once, allowing to send'
+ 'various mailings in a single campaign to test the effectiveness'
+ 'of the mailings.'),
+ 'color': fields.integer('Color Index'),
+ # stat fields
+ 'total': fields.function(
+ _get_statistics, string='Total',
+ type='integer', multi='_get_statistics'
+ ),
+ 'scheduled': fields.function(
+ _get_statistics, string='Scheduled',
+ type='integer', multi='_get_statistics'
+ ),
+ 'failed': fields.function(
+ _get_statistics, string='Failed',
+ type='integer', multi='_get_statistics'
+ ),
+ 'sent': fields.function(
+ _get_statistics, string='Sent Emails',
+ type='integer', multi='_get_statistics'
+ ),
+ 'delivered': fields.function(
+ _get_statistics, string='Delivered',
+ type='integer', multi='_get_statistics',
+ ),
+ 'opened': fields.function(
+ _get_statistics, string='Opened',
+ type='integer', multi='_get_statistics',
+ ),
+ 'replied': fields.function(
+ _get_statistics, string='Replied',
+ type='integer', multi='_get_statistics'
+ ),
+ 'bounced': fields.function(
+ _get_statistics, string='Bounced',
+ type='integer', multi='_get_statistics'
+ ),
+ 'received_ratio': fields.function(
+ _get_statistics, string='Received Ratio',
+ type='integer', multi='_get_statistics',
+ ),
+ 'opened_ratio': fields.function(
+ _get_statistics, string='Opened Ratio',
+ type='integer', multi='_get_statistics',
+ ),
+ 'replied_ratio': fields.function(
+ _get_statistics, string='Replied Ratio',
+ type='integer', multi='_get_statistics',
+ ),
+ }
+
+ def _get_default_stage_id(self, cr, uid, context=None):
+ stage_ids = self.pool['mail.mass_mailing.stage'].search(cr, uid, [], limit=1, context=context)
+ return stage_ids and stage_ids[0] or False
+
+ _defaults = {
+ 'user_id': lambda self, cr, uid, ctx=None: uid,
+ 'stage_id': lambda self, *args: self._get_default_stage_id(*args),
+ }
+
+ def get_recipients(self, cr, uid, ids, model=None, context=None):
+ """Return the recipients of a mailing campaign. This is based on the statistics
+ build for each mailing. """
+ Statistics = self.pool['mail.mail.statistics']
+ res = dict.fromkeys(ids, False)
+ for cid in ids:
+ domain = [('mass_mailing_campaign_id', '=', cid)]
+ if model:
+ domain += [('model', '=', model)]
+ stat_ids = Statistics.search(cr, uid, domain, context=context)
+ res[cid] = set(stat.res_id for stat in Statistics.browse(cr, uid, stat_ids, context=context))
+ return res
+
+
+class MassMailing(osv.Model):
+ """ MassMailing models a wave of emails for a mass mailign campaign.
+ A mass mailing is an occurence of sending emails. """
+
+ _name = 'mail.mass_mailing'
+ _description = 'Mass Mailing'
+ # number of periods for tracking mail_mail statistics
+ _period_number = 6
+ _order = 'sent_date DESC'
+
+ def __get_bar_values(self, cr, uid, obj, domain, read_fields, value_field, groupby_field, date_begin, context=None):
+ """ Generic method to generate data for bar chart values using SparklineBarWidget.
+ This method performs obj.read_group(cr, uid, domain, read_fields, groupby_field).
+
+ :param obj: the target model (i.e. crm_lead)
+ :param domain: the domain applied to the read_group
+ :param list read_fields: the list of fields to read in the read_group
+ :param str value_field: the field used to compute the value of the bar slice
+ :param str groupby_field: the fields used to group
+
+ :return list section_result: a list of dicts: [
+ { 'value': (int) bar_column_value,
+ 'tootip': (str) bar_column_tooltip,
+ }
+ ]
+ """
+ date_begin = date_begin.date()
+ section_result = [{'value': 0,
+ 'tooltip': (date_begin + relativedelta.relativedelta(days=i)).strftime('%d %B %Y'),
+ } for i in range(0, self._period_number)]
+ group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context)
+ field_col_info = obj._all_columns.get(groupby_field.split(':')[0])
+ pattern = tools.DEFAULT_SERVER_DATE_FORMAT if field_col_info.column._type == 'date' else tools.DEFAULT_SERVER_DATETIME_FORMAT
+ for group in group_obj:
+ group_begin_date = datetime.strptime(group['__domain'][0][2], pattern).date()
+ timedelta = relativedelta.relativedelta(group_begin_date, date_begin)
+ section_result[timedelta.days] = {'value': group.get(value_field, 0), 'tooltip': group.get(groupby_field)}
+ return section_result
+
+ def _get_daily_statistics(self, cr, uid, ids, field_name, arg, context=None):
+ """ Get the daily statistics of the mass mailing. This is done by a grouping
+ on opened and replied fields. Using custom format in context, we obtain
+ results for the next 6 days following the mass mailing date. """
+ obj = self.pool['mail.mail.statistics']
+ res = {}
+ for mailing in self.browse(cr, uid, ids, context=context):
+ res[mailing.id] = {}
+ date = mailing.sent_date if mailing.sent_date else mailing.create_date
+ date_begin = datetime.strptime(date, tools.DEFAULT_SERVER_DATETIME_FORMAT)
+ date_end = date_begin + relativedelta.relativedelta(days=self._period_number - 1)
+ date_begin_str = date_begin.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
+ date_end_str = date_end.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
+ domain = [('mass_mailing_id', '=', mailing.id), ('opened', '>=', date_begin_str), ('opened', '<=', date_end_str)]
+ res[mailing.id]['opened_dayly'] = json.dumps(self.__get_bar_values(cr, uid, obj, domain, ['opened'], 'opened_count', 'opened:day', date_begin, context=context))
+ domain = [('mass_mailing_id', '=', mailing.id), ('replied', '>=', date_begin_str), ('replied', '<=', date_end_str)]
+ res[mailing.id]['replied_dayly'] = json.dumps(self.__get_bar_values(cr, uid, obj, domain, ['replied'], 'replied_count', 'replied:day', date_begin, context=context))
+ return res
+
+ def _get_statistics(self, cr, uid, ids, name, arg, context=None):
+ """ Compute statistics of the mass mailing campaign """
+ Statistics = self.pool['mail.mail.statistics']
+ results = dict.fromkeys(ids, False)
+ for mid in ids:
+ stat_ids = Statistics.search(cr, uid, [('mass_mailing_id', '=', mid)], context=context)
+ stats = Statistics.browse(cr, uid, stat_ids, context=context)
+ results[mid] = {
+ 'total': len(stats),
+ 'failed': len([s for s in stats if not s.scheduled is False and s.sent is False and not s.exception is False]),
+ 'scheduled': len([s for s in stats if not s.scheduled is False and s.sent is False and s.exception is False]),
+ 'sent': len([s for s in stats if not s.sent is False]),
+ 'opened': len([s for s in stats if not s.opened is False]),
+ 'replied': len([s for s in stats if not s.replied is False]),
+ 'bounced': len([s for s in stats if not s.bounced is False]),
+ }
+ results[mid]['delivered'] = results[mid]['sent'] - results[mid]['bounced']
+ results[mid]['received_ratio'] = 100.0 * results[mid]['delivered'] / (results[mid]['total'] or 1)
+ results[mid]['opened_ratio'] = 100.0 * results[mid]['opened'] / (results[mid]['total'] or 1)
+ results[mid]['replied_ratio'] = 100.0 * results[mid]['replied'] / (results[mid]['total'] or 1)
+ return results
+
+ def _get_mailing_model(self, cr, uid, context=None):
+ res = []
+ for model_name in self.pool:
+ model = self.pool[model_name]
+ if hasattr(model, '_mail_mass_mailing') and getattr(model, '_mail_mass_mailing'):
+ res.append((model._name, getattr(model, '_mail_mass_mailing')))
+ res.append(('mail.mass_mailing.contact', _('Mailing List')))
+ return res
+
+ # indirections for inheritance
+ _mailing_model = lambda self, *args, **kwargs: self._get_mailing_model(*args, **kwargs)
+
+ _columns = {
+ 'name': fields.char('Subject', required=True),
+ 'email_from': fields.char('From', required=True),
+ 'create_date': fields.datetime('Creation Date'),
+ 'sent_date': fields.datetime('Sent Date'),
+ 'body_html': fields.html('Body'),
+ 'attachment_ids': fields.many2many(
+ 'ir.attachment', 'mass_mailing_ir_attachments_rel',
+ 'mass_mailing_id', 'attachment_id', 'Attachments'
+ ),
+ 'mass_mailing_campaign_id': fields.many2one(
+ 'mail.mass_mailing.campaign', 'Mass Mailing Campaign',
+ ondelete='set null',
+ ),
+ 'state': fields.selection(
+ [('draft', 'Draft'), ('test', 'Tested'), ('done', 'Sent')],
+ string='Status', required=True,
+ ),
+ 'color': fields.related(
+ 'mass_mailing_campaign_id', 'color',
+ type='integer', string='Color Index',
+ ),
+ # mailing options
+ 'reply_to_mode': fields.selection(
+ [('thread', 'In Document'), ('email', 'Specified Email Address')],
+ string='Reply-To Mode', required=True,
+ ),
+ 'reply_to': fields.char('Reply To', help='Preferred Reply-To Address'),
+ # recipients
+ 'mailing_model': fields.selection(_mailing_model, string='Recipients Model', required=True),
+ 'mailing_domain': fields.char('Domain'),
+ 'contact_list_ids': fields.many2many(
+ 'mail.mass_mailing.list', 'mail_mass_mailing_list_rel',
+ string='Mailing Lists',
+ ),
+ 'contact_ab_pc': fields.integer(
+ 'AB Testing percentage',
+ help='Percentage of the contacts that will be mailed. Recipients will be taken randomly.'
+ ),
+ # statistics data
+ 'statistics_ids': fields.one2many(
+ 'mail.mail.statistics', 'mass_mailing_id',
+ 'Emails Statistics',
+ ),
+ 'total': fields.function(
+ _get_statistics, string='Total',
+ type='integer', multi='_get_statistics',
+ ),
+ 'scheduled': fields.function(
+ _get_statistics, string='Scheduled',
+ type='integer', multi='_get_statistics',
+ ),
+ 'failed': fields.function(
+ _get_statistics, string='Failed',
+ type='integer', multi='_get_statistics',
+ ),
+ 'sent': fields.function(
+ _get_statistics, string='Sent',
+ type='integer', multi='_get_statistics',
+ ),
+ 'delivered': fields.function(
+ _get_statistics, string='Delivered',
+ type='integer', multi='_get_statistics',
+ ),
+ 'opened': fields.function(
+ _get_statistics, string='Opened',
+ type='integer', multi='_get_statistics',
+ ),
+ 'replied': fields.function(
+ _get_statistics, string='Replied',
+ type='integer', multi='_get_statistics',
+ ),
+ 'bounced': fields.function(
+ _get_statistics, string='Bounced',
+ type='integer', multi='_get_statistics',
+ ),
+ 'received_ratio': fields.function(
+ _get_statistics, string='Received Ratio',
+ type='integer', multi='_get_statistics',
+ ),
+ 'opened_ratio': fields.function(
+ _get_statistics, string='Opened Ratio',
+ type='integer', multi='_get_statistics',
+ ),
+ 'replied_ratio': fields.function(
+ _get_statistics, string='Replied Ratio',
+ type='integer', multi='_get_statistics',
+ ),
+ # dayly ratio
+ 'opened_dayly': fields.function(
+ _get_daily_statistics, string='Opened',
+ type='char', multi='_get_daily_statistics',
+ oldname='opened_monthly',
+ ),
+ 'replied_dayly': fields.function(
+ _get_daily_statistics, string='Replied',
+ type='char', multi='_get_daily_statistics',
+ oldname='replied_monthly',
+ )
+ }
+
+ def default_get(self, cr, uid, fields, context=None):
+ res = super(MassMailing, self).default_get(cr, uid, fields, context=context)
+ if 'reply_to_mode' in fields and not 'reply_to_mode' in res and res.get('mailing_model'):
+ if res['mailing_model'] in ['res.partner', 'mail.mass_mailing.contact']:
+ res['reply_to_mode'] = 'email'
+ else:
+ res['reply_to_mode'] = 'thread'
+ return res
+
+ _defaults = {
+ 'state': 'draft',
+ 'email_from': lambda self, cr, uid, ctx=None: self.pool['mail.message']._get_default_from(cr, uid, context=ctx),
+ 'reply_to': lambda self, cr, uid, ctx=None: self.pool['mail.message']._get_default_from(cr, uid, context=ctx),
+ 'mailing_model': 'mail.mass_mailing.contact',
+ 'contact_ab_pc': 100,
+ }
+
+ #------------------------------------------------------
+ # Technical stuff
+ #------------------------------------------------------
+
+ def copy_data(self, cr, uid, id, default=None, context=None):
+ if default is None:
+ default = {}
+ mailing = self.browse(cr, uid, id, context=context)
+ default.update({
+ 'state': 'draft',
+ 'statistics_ids': [],
+ 'name': _('%s (duplicate)') % mailing.name,
+ 'sent_date': False,
+ })
+ return super(MassMailing, self).copy_data(cr, uid, id, default, context=context)
+
+ def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False, lazy=True):
+ """ Override read_group to always display all states. """
+ if groupby and groupby[0] == "state":
+ # Default result structure
+ # states = self._get_state_list(cr, uid, context=context)
+ states = [('draft', 'Draft'), ('test', 'Tested'), ('done', 'Sent')]
+ read_group_all_states = [{
+ '__context': {'group_by': groupby[1:]},
+ '__domain': domain + [('state', '=', state_value)],
+ 'state': state_value,
+ 'state_count': 0,
+ } for state_value, state_name in states]
+ # Get standard results
+ read_group_res = super(MassMailing, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby)
+ # Update standard results with default results
+ result = []
+ for state_value, state_name in states:
+ res = filter(lambda x: x['state'] == state_value, read_group_res)
+ if not res:
+ res = filter(lambda x: x['state'] == state_value, read_group_all_states)
+ res[0]['state'] = [state_value, state_name]
+ result.append(res[0])
+ return result
+ else:
+ return super(MassMailing, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby)
+
+ #------------------------------------------------------
+ # Views & Actions
+ #------------------------------------------------------
+
+ def on_change_model_and_list(self, cr, uid, ids, mailing_model, list_ids, context=None):
+ value = {}
+ if mailing_model == 'mail.mass_mailing.contact':
+ list_ids = map(lambda item: item if isinstance(item, (int, long)) else [lid for lid in item[2]], list_ids)
+ if list_ids:
+ value['mailing_domain'] = "[('list_id', 'in', %s)]" % list_ids
+ else:
+ value['mailing_domain'] = "[('list_id', '=', False)]"
+ else:
+ value['mailing_domain'] = False
+ return {'value': value}
+
+ def action_duplicate(self, cr, uid, ids, context=None):
+ copy_id = None
+ for mid in ids:
+ copy_id = self.copy(cr, uid, mid, context=context)
+ if copy_id:
+ return {
+ 'type': 'ir.actions.act_window',
+ 'view_type': 'form',
+ 'view_mode': 'form',
+ 'res_model': 'mail.mass_mailing',
+ 'res_id': copy_id,
+ 'context': context,
+ }
+ return False
+
+ def action_test_mailing(self, cr, uid, ids, context=None):
+ ctx = dict(context, default_mass_mailing_id=ids[0])
+ return {
+ 'name': _('Test Mailing'),
+ 'type': 'ir.actions.act_window',
+ 'view_mode': 'form',
+ 'res_model': 'mail.mass_mailing.test',
+ 'target': 'new',
+ 'context': ctx,
+ }
+
+ def action_edit_html(self, cr, uid, ids, context=None):
+ if not len(ids) == 1:
+ raise ValueError('One and only one ID allowed for this action')
+ mail = self.browse(cr, uid, ids[0], context=context)
+ url = '/website_mail/email_designer?model=mail.mass_mailing&res_id=%d&template_model=%s&enable_editor=1' % (ids[0], mail.mailing_model)
+ return {
+ 'name': _('Open with Visual Editor'),
+ 'type': 'ir.actions.act_url',
+ 'url': url,
+ 'target': 'self',
+ }
+
+ #------------------------------------------------------
+ # Email Sending
+ #------------------------------------------------------
+
+ def get_recipients(self, cr, uid, mailing, context=None):
+ domain = eval(mailing.mailing_domain)
+ res_ids = self.pool[mailing.mailing_model].search(cr, uid, domain, context=context)
+
+ # randomly choose a fragment
+ if mailing.contact_ab_pc < 100:
+ contact_nbr = self.pool[mailing.mailing_model].search(cr, uid, domain, count=True, context=context)
+ topick = int(contact_nbr / 100.0 * mailing.contact_ab_pc)
+ if mailing.mass_mailing_campaign_id and mailing.mass_mailing_campaign_id.unique_ab_testing:
+ already_mailed = self.pool['mail.mass_mailing.campaign'].get_recipients(cr, uid, [mailing.mass_mailing_campaign_id.id], context=context)[mailing.mass_mailing_campaign_id.id]
+ else:
+ already_mailed = set([])
+ remaining = set(res_ids).difference(already_mailed)
+ if topick > len(remaining):
+ topick = len(remaining)
+ res_ids = random.sample(remaining, topick)
+ return res_ids
+
+ def send_mail(self, cr, uid, ids, context=None):
+ author_id = self.pool['res.users'].browse(cr, uid, uid, context=context).partner_id.id
+ for mailing in self.browse(cr, uid, ids, context=context):
+ # instantiate an email composer + send emails
+ res_ids = self.get_recipients(cr, uid, mailing, context=context)
+ comp_ctx = dict(context, active_ids=res_ids)
+ composer_values = {
+ 'author_id': author_id,
+ 'body': mailing.body_html,
+ 'subject': mailing.name,
+ 'model': mailing.mailing_model,
+ 'email_from': mailing.email_from,
+ 'record_name': False,
+ 'composition_mode': 'mass_mail',
+ 'mass_mailing_id': mailing.id,
+ 'mailing_list_ids': [(4, l.id) for l in mailing.contact_list_ids],
+ }
+ if mailing.reply_to_mode == 'email':
+ composer_values['reply_to'] = mailing.reply_to
+ composer_id = self.pool['mail.compose.message'].create(cr, uid, composer_values, context=comp_ctx)
+ self.pool['mail.compose.message'].send_mail(cr, uid, [composer_id], context=comp_ctx)
+ self.write(cr, uid, [mailing.id], {'sent_date': fields.datetime.now(), 'state': 'done'}, context=context)
+ return True