Source code for scrapy.mail

"""
Mail sending helpers

See documentation in docs/topics/email.rst
"""
from io import BytesIO
import logging

from email.utils import COMMASPACE, formatdate
from six.moves.email_mime_multipart import MIMEMultipart
from six.moves.email_mime_text import MIMEText
from six.moves.email_mime_base import MIMEBase
from email.mime.nonmultipart import MIMENonMultipart
from email import encoders as Encoders

from twisted.internet import defer, reactor, ssl

from scrapy.utils.misc import arg_to_iter
from scrapy.utils.python import to_bytes

logger = logging.getLogger(__name__)


def _to_bytes_or_none(text):
    if text is None:
        return None
    return to_bytes(text)


[docs]class MailSender(object): def __init__(self, smtphost='localhost', mailfrom='scrapy@localhost', smtpuser=None, smtppass=None, smtpport=25, smtptls=False, smtpssl=False, debug=False): self.smtphost = smtphost self.smtpport = smtpport self.smtpuser = _to_bytes_or_none(smtpuser) self.smtppass = _to_bytes_or_none(smtppass) self.smtptls = smtptls self.smtpssl = smtpssl self.mailfrom = mailfrom self.debug = debug
[docs] @classmethod def from_settings(cls, settings): return cls(settings['MAIL_HOST'], settings['MAIL_FROM'], settings['MAIL_USER'], settings['MAIL_PASS'], settings.getint('MAIL_PORT'), settings.getbool('MAIL_TLS'), settings.getbool('MAIL_SSL'))
[docs] def send(self, to, subject, body, cc=None, attachs=(), mimetype='text/plain', charset=None, _callback=None): if attachs: msg = MIMEMultipart() else: msg = MIMENonMultipart(*mimetype.split('/', 1)) to = list(arg_to_iter(to)) cc = list(arg_to_iter(cc)) msg['From'] = self.mailfrom msg['To'] = COMMASPACE.join(to) msg['Date'] = formatdate(localtime=True) msg['Subject'] = subject rcpts = to[:] if cc: rcpts.extend(cc) msg['Cc'] = COMMASPACE.join(cc) if charset: msg.set_charset(charset) if attachs: msg.attach(MIMEText(body, 'plain', charset or 'us-ascii')) for attach_name, mimetype, f in attachs: part = MIMEBase(*mimetype.split('/')) part.set_payload(f.read()) Encoders.encode_base64(part) part.add_header('Content-Disposition', 'attachment; filename="%s"' \ % attach_name) msg.attach(part) else: msg.set_payload(body) if _callback: _callback(to=to, subject=subject, body=body, cc=cc, attach=attachs, msg=msg) if self.debug: logger.debug('Debug mail sent OK: To=%(mailto)s Cc=%(mailcc)s ' 'Subject="%(mailsubject)s" Attachs=%(mailattachs)d', {'mailto': to, 'mailcc': cc, 'mailsubject': subject, 'mailattachs': len(attachs)}) return dfd = self._sendmail(rcpts, msg.as_string().encode(charset or 'utf-8')) dfd.addCallbacks(self._sent_ok, self._sent_failed, callbackArgs=[to, cc, subject, len(attachs)], errbackArgs=[to, cc, subject, len(attachs)]) reactor.addSystemEventTrigger('before', 'shutdown', lambda: dfd) return dfd
def _sent_ok(self, result, to, cc, subject, nattachs): logger.info('Mail sent OK: To=%(mailto)s Cc=%(mailcc)s ' 'Subject="%(mailsubject)s" Attachs=%(mailattachs)d', {'mailto': to, 'mailcc': cc, 'mailsubject': subject, 'mailattachs': nattachs}) def _sent_failed(self, failure, to, cc, subject, nattachs): errstr = str(failure.value) logger.error('Unable to send mail: To=%(mailto)s Cc=%(mailcc)s ' 'Subject="%(mailsubject)s" Attachs=%(mailattachs)d' '- %(mailerr)s', {'mailto': to, 'mailcc': cc, 'mailsubject': subject, 'mailattachs': nattachs, 'mailerr': errstr}) def _sendmail(self, to_addrs, msg): # Import twisted.mail here because it is not available in python3 from twisted.mail.smtp import ESMTPSenderFactory msg = BytesIO(msg) d = defer.Deferred() factory = ESMTPSenderFactory(self.smtpuser, self.smtppass, self.mailfrom, \ to_addrs, msg, d, heloFallback=True, requireAuthentication=False, \ requireTransportSecurity=self.smtptls) factory.noisy = False if self.smtpssl: reactor.connectSSL(self.smtphost, self.smtpport, factory, ssl.ClientContextFactory()) else: reactor.connectTCP(self.smtphost, self.smtpport, factory) return d