1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2010-today OpenERP SA (<http://www.openerp.com>)
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>
20 ##############################################################################
28 from osv import fields
30 _logger = logging.getLogger(__name__)
32 class mail_mail(osv.Model):
34 Model holding RFC2822 email messages to send. This model also provides
35 facilities to queue and send new email messages.
39 _description = 'Outgoing Mails'
40 _inherits = {'mail.message': 'mail_message_id'}
42 'mail_message_id': fields.many2one('mail.message', 'Message', required=True, ondelete='cascade'),
43 'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing mail server', readonly=1),
44 'state': fields.selection([
45 ('outgoing', 'Outgoing'),
47 ('received', 'Received'),
48 ('exception', 'Delivery Failed'),
49 ('cancel', 'Cancelled'),
50 ], 'Status', readonly=True),
51 'auto_delete': fields.boolean('Auto Delete',
52 help="Permanently delete this email after sending it, to save space"),
53 'references': fields.text('References', help='Message references, such as identifiers of previous messages', readonly=1),
54 'email_from': fields.char('From', size=128, help='Message sender, taken from user preferences.'),
55 'email_to': fields.text('To', help='Message recipients'),
56 'email_cc': fields.char('Cc', size=256, help='Carbon copy message recipients'),
57 'reply_to':fields.char('Reply-To', size=256, help='Preferred response address for the message'),
59 'content_subtype': fields.char('Message content subtype', size=32,
60 oldname="subtype", readonly=1,
61 help="Type of message, usually 'html' or 'plain', used to select "\
62 "plain-text or rich-text contents accordingly"),
63 'body_html': fields.text('Rich-text Contents', help="Rich-text/HTML version of the message"),
66 def _get_default_from(self, cr, uid, context={}):
67 cur = self.pool.get('res.users').browse(cr, uid, uid, context=context)
68 if not cur.alias_domain:
69 raise osv.except_osv(_('Invalid Action!'), _('Unable to send email, set an alias domain in your server settings.'))
70 return cur.alias_name + '@' + cur.alias_domain
74 'content_subtype': 'plain',
75 'email_from': _get_default_from
78 def mark_outgoing(self, cr, uid, ids, context=None):
79 return self.write(cr, uid, ids, {'state':'outgoing'}, context=context)
81 def cancel(self, cr, uid, ids, context=None):
82 return self.write(cr, uid, ids, {'state':'cancel'}, context=context)
84 def process_email_queue(self, cr, uid, ids=None, context=None):
85 """Send immediately queued messages, committing after each
86 message is sent - this is not transactional and should
87 not be called during another transaction!
89 :param list ids: optional list of emails ids to send. If passed
90 no search is performed, and these ids are used
92 :param dict context: if a 'filters' key is present in context,
93 this value will be used as an additional
94 filter to further restrict the outgoing
95 messages to send (by default all 'outgoing'
101 filters = ['&', ('state', '=', 'outgoing'), ('type', '=', 'email')]
102 if 'filters' in context:
103 filters.extend(context['filters'])
104 ids = self.search(cr, uid, filters, context=context)
107 # Force auto-commit - this is meant to be called by
108 # the scheduler, and we can't allow rolling back the status
109 # of previously sent emails!
110 res = self.send(cr, uid, ids, auto_commit=True, context=context)
112 _logger.exception("Failed processing mail queue")
115 def _postprocess_sent_message(self, cr, uid, message, context=None):
116 """Perform any post-processing necessary after sending ``message``
117 successfully, including deleting it completely along with its
118 attachment if the ``auto_delete`` flag of the message was set.
119 Overridden by subclasses for extra post-processing behaviors.
121 :param browse_record message: the message that was just sent
124 if message.auto_delete:
125 self.pool.get('ir.attachment').unlink(cr, uid,
126 [x.id for x in message.attachment_ids],
131 def send(self, cr, uid, ids, auto_commit=False, context=None):
132 """Sends the selected emails immediately, ignoring their current
133 state (mails that have already been sent should not be passed
134 unless they should actually be re-sent).
135 Emails successfully delivered are marked as 'sent', and those
136 that fail to be deliver are marked as 'exception', and the
137 corresponding error message is output in the server logs.
139 :param bool auto_commit: whether to force a commit of the message
140 status after sending each message (meant
141 only for processing by the scheduler),
142 should never be True during normal
143 transactions (default: False)
146 ir_mail_server = self.pool.get('ir.mail_server')
147 self.write(cr, uid, ids, {'state': 'outgoing'}, context=context)
148 for message in self.browse(cr, uid, ids, context=context):
151 for attach in message.attachment_ids:
152 attachments.append((attach.datas_fname, base64.b64decode(attach.datas)))
154 body = message.body_html if message.content_subtype == 'html' else message.body
155 body_alternative = None
156 content_subtype_alternative = None
157 if message.content_subtype == 'html':
158 # we have a plain text alternative prepared, pass it to
159 # build_message instead of letting it build one
160 body_alternative = tools.html2plaintext(message.body)
161 content_subtype_alternative = 'plain'
163 # handle destination_partners
164 partner_ids_email_to = ''
165 for partner in message.partner_ids:
166 partner_ids_email_to += '%s ' % (partner.email or '')
167 message_email_to = '%s %s' % (partner_ids_email_to, message.email_to or '')
169 # build an RFC2822 email.message.Message object and send it
171 msg = ir_mail_server.build_email(
172 email_from=message.email_from,
173 email_to=tools.email_split(message_email_to),
174 subject=message.subject,
176 body_alternative=body_alternative,
177 email_cc=tools.email_split(message.email_cc),
178 reply_to=message.reply_to,
179 attachments=attachments, message_id=message.message_id,
180 references = message.references,
181 object_id=message.res_id and ('%s-%s' % (message.res_id,message.model)),
182 subtype=message.content_subtype,
183 subtype_alternative=content_subtype_alternative)
184 res = ir_mail_server.send_email(cr, uid, msg,
185 mail_server_id=message.mail_server_id.id,
188 message.write({'state':'sent', 'message_id': res, 'email_to': message_email_to})
190 message.write({'state':'exception', 'email_to': message_email_to})
192 if message.state == 'sent':
193 self._postprocess_sent_message(cr, uid, message, context=context)
195 _logger.exception('failed sending mail.mail %s', message.id)
196 message.write({'state':'exception'})
198 if auto_commit == True: