added tests for SpiderState extension
[scrapy.git] / scrapy / mail.py
bloba7e4c94f7aa39b3c8b6ba13bcde48db05a6d3db8
1 """
2 Mail sending helpers
4 See documentation in docs/topics/email.rst
5 """
6 from cStringIO import StringIO
7 from email.MIMEMultipart import MIMEMultipart
8 from email.MIMENonMultipart import MIMENonMultipart
9 from email.MIMEBase import MIMEBase
10 from email.MIMEText import MIMEText
11 from email.Utils import COMMASPACE, formatdate
12 from email import Encoders
14 from twisted.internet import defer, reactor
15 from twisted.mail.smtp import ESMTPSenderFactory
17 from scrapy import log
18 from scrapy.exceptions import NotConfigured
19 from scrapy.conf import settings
20 from scrapy.utils.signal import send_catch_log
23 # signal sent when message is sent
24 # args: to, subject, body, cc, attach, msg
25 mail_sent = object()
28 class MailSender(object):
30 def __init__(self, smtphost=None, mailfrom=None, smtpuser=None, smtppass=None, \
31 smtpport=None, debug=False):
32 self.smtphost = smtphost or settings['MAIL_HOST']
33 self.smtpport = smtpport or settings.getint('MAIL_PORT')
34 self.smtpuser = smtpuser or settings['MAIL_USER']
35 self.smtppass = smtppass or settings['MAIL_PASS']
36 self.mailfrom = mailfrom or settings['MAIL_FROM']
37 self.debug = debug
39 if not self.smtphost or not self.mailfrom:
40 raise NotConfigured("MAIL_HOST and MAIL_FROM settings are required")
42 def send(self, to, subject, body, cc=None, attachs=()):
43 if attachs:
44 msg = MIMEMultipart()
45 else:
46 msg = MIMENonMultipart('text', 'plain')
47 msg['From'] = self.mailfrom
48 msg['To'] = COMMASPACE.join(to)
49 msg['Date'] = formatdate(localtime=True)
50 msg['Subject'] = subject
51 rcpts = to[:]
52 if cc:
53 rcpts.extend(cc)
54 msg['Cc'] = COMMASPACE.join(cc)
56 if attachs:
57 msg.attach(MIMEText(body))
58 for attach_name, mimetype, f in attachs:
59 part = MIMEBase(*mimetype.split('/'))
60 part.set_payload(f.read())
61 Encoders.encode_base64(part)
62 part.add_header('Content-Disposition', 'attachment; filename="%s"' \
63 % attach_name)
64 msg.attach(part)
65 else:
66 msg.set_payload(body)
68 send_catch_log(signal=mail_sent, to=to, subject=subject, body=body,
69 cc=cc, attach=attachs, msg=msg)
71 if self.debug:
72 log.msg('Debug mail sent OK: To=%s Cc=%s Subject="%s" Attachs=%d' % \
73 (to, cc, subject, len(attachs)), level=log.DEBUG)
74 return
76 dfd = self._sendmail(rcpts, msg.as_string())
77 dfd.addCallbacks(self._sent_ok, self._sent_failed,
78 callbackArgs=[to, cc, subject, len(attachs)],
79 errbackArgs=[to, cc, subject, len(attachs)])
80 reactor.addSystemEventTrigger('before', 'shutdown', lambda: dfd)
81 return dfd
83 def _sent_ok(self, result, to, cc, subject, nattachs):
84 log.msg('Mail sent OK: To=%s Cc=%s Subject="%s" Attachs=%d' % \
85 (to, cc, subject, nattachs))
87 def _sent_failed(self, failure, to, cc, subject, nattachs):
88 errstr = str(failure.value)
89 log.msg('Unable to send mail: To=%s Cc=%s Subject="%s" Attachs=%d - %s' % \
90 (to, cc, subject, nattachs, errstr), level=log.ERROR)
92 def _sendmail(self, to_addrs, msg):
93 msg = StringIO(msg)
94 d = defer.Deferred()
95 factory = ESMTPSenderFactory(self.smtpuser, self.smtppass, self.mailfrom, \
96 to_addrs, msg, d, heloFallback=True, requireAuthentication=False, \
97 requireTransportSecurity=False)
98 factory.noisy = False
99 reactor.connectTCP(self.smtphost, self.smtpport, factory)
100 return d