Repository: scrapy/scrapy

Issue:
Allow sending HTML emails with scrapy.mail.MailSender
I've patched this locally by changing the `send` method:

```
def send(self, to, subject, body, cc=None, attachs=(), mime='text/plain', _callback=None):
    if attachs:
        msg = MIMEMultipart()
    else:
        msg = MIMENonMultipart(*mime.split('/'))
```

But it seems fragile. Any thoughts?

Allow sending HTML emails with scrapy.mail.MailSender
I've patched this locally by changing the `send` method:

```
def send(self, to, subject, body, cc=None, attachs=(), mime='text/plain', _callback=None):
    if attachs:
        msg = MIMEMultipart()
    else:
        msg = MIMENonMultipart(*mime.split('/'))
```

But it seems fragile. Any thoughts?



Discussion:
What you're proposing is to add a `mime` parameter to `MailSender.send` method? What for?

The default mimetype is `text/plain`, which renders HTML as plain text. Maybe just an `html` parameter that defaults to `False` will work?

@Blender3D : are you going to send a PR wit this change including tests? 

Well, I posted an issue without a pull request to get feedback on the
different alternatives, as they both work and are equally simple to
implement.

On Tue, Nov 19, 2013 at 12:36 PM, Daniel Graña notifications@github.comwrote:

> @Blender3D https://github.com/Blender3D : are you going to send a PR
> wit this change including tests?
> 
> —
> Reply to this email directly or view it on GitHubhttps://github.com/scrapy/scrapy/issues/441#issuecomment-28813314
> .

@Blender3D : I dont get what are the alternatives here (plural).

I only see a new parameter named `mime` to `MailSender.send()`, and it makes sense to me.

I infer your mention it's fragile because of `*mime.split('/')`, which should be fine if you include tests.

We can use this method with no use of MIMENonMultipart() to render the text as both plain
and Html.

```
def send(self, to, subject, body, cc=None, attachs=(), _callback=None):
    if attachs:
        msg = MIMEMultipart()
    else:
        part1 = MIMEText(text, 'plain')
        part2 = MIMEText(html, 'html')
        msg.attach(part1)
        msg.attach(part2)
```

But sometimes in this exception email.errors.MultipartConversionError  occurs. It can be easily handled.

What you're proposing is to add a `mime` parameter to `MailSender.send` method? What for?

The default mimetype is `text/plain`, which renders HTML as plain text. Maybe just an `html` parameter that defaults to `False` will work?

@Blender3D : are you going to send a PR wit this change including tests? 

Well, I posted an issue without a pull request to get feedback on the
different alternatives, as they both work and are equally simple to
implement.

On Tue, Nov 19, 2013 at 12:36 PM, Daniel Graña notifications@github.comwrote:

> @Blender3D https://github.com/Blender3D : are you going to send a PR
> wit this change including tests?
> 
> —
> Reply to this email directly or view it on GitHubhttps://github.com/scrapy/scrapy/issues/441#issuecomment-28813314
> .

@Blender3D : I dont get what are the alternatives here (plural).

I only see a new parameter named `mime` to `MailSender.send()`, and it makes sense to me.

I infer your mention it's fragile because of `*mime.split('/')`, which should be fine if you include tests.

We can use this method with no use of MIMENonMultipart() to render the text as both plain
and Html.

```
def send(self, to, subject, body, cc=None, attachs=(), _callback=None):
    if attachs:
        msg = MIMEMultipart()
    else:
        part1 = MIMEText(text, 'plain')
        part2 = MIMEText(html, 'html')
        msg.attach(part1)
        msg.attach(part2)
```

But sometimes in this exception email.errors.MultipartConversionError  occurs. It can be easily handled.


Resolving patch:
diff --git a/scrapy/mail.py b/scrapy/mail.py
--- a/scrapy/mail.py
+++ b/scrapy/mail.py
@@ -35,11 +35,11 @@ def from_settings(cls, settings):
             settings['MAIL_PASS'], settings.getint('MAIL_PORT'),
             settings.getbool('MAIL_TLS'), settings.getbool('MAIL_SSL'))
 
-    def send(self, to, subject, body, cc=None, attachs=(), _callback=None):
+    def send(self, to, subject, body, cc=None, attachs=(), mimetype='text/plain', _callback=None):
         if attachs:
             msg = MIMEMultipart()
         else:
-            msg = MIMENonMultipart('text', 'plain')
+            msg = MIMENonMultipart(*mimetype.split('/', 1))
         msg['From'] = self.mailfrom
         msg['To'] = COMMASPACE.join(to)
         msg['Date'] = formatdate(localtime=True)


Test patch:
diff --git a/scrapy/tests/test_mail.py b/scrapy/tests/test_mail.py
--- a/scrapy/tests/test_mail.py
+++ b/scrapy/tests/test_mail.py
@@ -1,6 +1,6 @@
-from cStringIO import StringIO
 import unittest
 
+from cStringIO import StringIO
 from scrapy.mail import MailSender
 
 class MailSenderTest(unittest.TestCase):
@@ -19,6 +19,15 @@ def test_send(self):
         self.assertEqual(msg['to'], 'test@scrapy.org')
         self.assertEqual(msg['subject'], 'subject')
         self.assertEqual(msg.get_payload(), 'body')
+        self.assertEqual(msg.get('Content-Type'), 'text/plain')
+
+    def test_send_html(self):
+        mailsender = MailSender(debug=True)
+        mailsender.send(to=['test@scrapy.org'], subject='subject', body='<p>body</p>', mimetype='text/html', _callback=self._catch_mail_sent)
+
+        msg = self.catched_msg['msg']
+        self.assertEqual(msg.get_payload(), '<p>body</p>')
+        self.assertEqual(msg.get('Content-Type'), 'text/html')
 
     def test_send_attach(self):
         attach = StringIO()