Search code examples
pythonsmtptwisted

Python and Twisted: making utf-8 html mail with message-id


I am trying to send emails to Gmail with Twisted, but having some strange problems.

Firstly, this method works fine for simple text-only messages. But when I am tried to use this snippet with cStringIO data from this article, I've got empty message without title and my e-mail as recipient.

Now, I think, that I forgot something simple, but I've got nothing, trying to find answer today. So, final source with my problem is:

from twisted.internet import defer
from twisted.mail import smtp, relaymanager
from twisted.internet import reactor
from cStringIO import StringIO

from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.header import Header
from email import Charset
from email.generator import Generator
from email.Utils import make_msgid

MXCALCULATOR = relaymanager.MXCalculator()

def getMailExchange(host):
    def cbMX(mxRecord):
        return str(mxRecord.name)
    return MXCALCULATOR.getMX(host).addCallback(cbMX)

def sendEmail(mailFrom, mailTo, msg, subject=""):
    def dosend(host):
        print "emailing %s (using host %s) from %s" % (mailTo, host, mailFrom)

        html = u'<html><body>You\'re registered. Now you should use site.</body></html>'
        text = u'You\'re registered. Now you should use site.'

        # Override python's weird assumption that utf-8 text should be encoded with
        # base64, and instead use quoted-printable (for both subject and body).  I
        # can't figure out a way to specify QP (quoted-printable) instead of base64 in
        # a way that doesn't modify global state. :-(
        Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8')

        # This example is of an email with text and html alternatives.
        multipart = MIMEMultipart('alternative')

        # We need to use Header objects here instead of just assigning the strings in
        # order to get our headers properly encoded (with QP).
        # You may want to avoid this if your headers are already ASCII, just so people
        # can read the raw message without getting a headache.
        multipart['Subject'] = Header(subject.encode('utf-8'), 'UTF-8').encode()
        multipart['To'] = Header(mailTo.encode('utf-8'), 'UTF-8').encode()
        multipart['From'] = Header(mailFrom.encode('utf-8'), 'UTF-8').encode()
        multipart['Message-Id'] = Header(make_msgid('e_shop').encode('utf-8'), 'UTF-8').encode()

        # Attach the parts with the given encodings.
        htmlpart = MIMEText(html.encode('utf-8'), 'html', 'UTF-8')
        multipart.attach(htmlpart)
        textpart = MIMEText(text.encode('utf-8'), 'plain', 'UTF-8')
        multipart.attach(textpart)

        # And here we have to instantiate a Generator object to convert the multipart
        # object to a string (can't use multipart.as_string, because that escapes
        # "From" lines).

        io = StringIO()
        g = Generator(io, False) # second argument means "should I mangle From?"
        g.flatten(multipart)

        d = defer.Deferred()
        factory = smtp.ESMTPSenderFactory(None, None, mailFrom, mailTo, io, d,
                                          requireAuthentication=False,
                                          requireTransportSecurity=False)
        reactor.connectTCP(host, 25, factory)
        return d
    return getMailExchange(mailTo.split("@")[1]).addCallback(dosend)

d = sendEmail('[email protected]', '[email protected]', 'template filename', 'this is a test subject')
d.addCallback(lambda _: reactor.stop())

reactor.run()

What should I change to make it work?


Solution

  • The following is working for me (minus subs) it's a multipart message - maybe you can get message-id going:

    fn = "example.mp3"
    multipart = MIMEMultipart('alternative')
    multipart['Subject'] = 'Tutorate!'
    multipart['To'] = 'Selfie'
    multipart['From'] = 'Selfie'
    
    text = "Hello, how are you, goodbye."
    textpart = MIMEText(text)
    multipart.attach(textpart)
    htmlpart = MIMEText("<html>" + text + "</html>", 'html')
    multipart.attach(htmlpart)
    
    part = MIMEBase('audio', "mp3")
    part.set_payload( open(fn,"rb").read() )
    Encoders.encode_base64(part)
    part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(fn))
    multipart.attach(part)
    
    io = StringIO.StringIO()
    g = Generator(io, False)  # second argument means "should I mangle From?"
    g.flatten(multipart)
    v = io.getvalue()
    
    class SMTPTutorialClient(smtp.ESMTPClient):
        mailFrom = "selfie@~"
        mailTo = "selfie@~"
        def getMailFrom(self):
            result = self.mailFrom
            self.mailFrom = None
            return result
    
        def getMailTo(self):
            return [self.mailTo]
    
        def getMailData(self):
            #print v
            return StringIO.StringIO(v)
    
        def sentMail(self, code, resp, numOk, addresses, log):
            print 'Sent', numOk, 'messages'
    
            from twisted.internet import reactor
            reactor.stop()