4

I want to write a HTML mail in Python/Django containing these parts:

  • HTML linking to logo.png
  • logo.png which should be displayed inline (not as attachment) in the mail user agent
  • info.pdf which should be displayed as attachment
  • Text which should be displayed if the mail user agent can't display HTML.

I followed this blog post.

Result:

  • The HTML and the inline image works
  • but the info.pdf file gets treated like the inline logo.png, and some mail user agent don't show it :-(

How to create both ways (download (info.pdf) and inline (logo.png)) in one mail in python?

6
  • Does this help ? : stackoverflow.com/a/20717538/2286762 Commented Nov 3, 2016 at 5:41
  • @soupboy no, it does not help. The question you reference does only solve one way of attaching a file.
    – guettli
    Commented Nov 3, 2016 at 13:50
  • 1
    but inline image part you were able to implement right? Can you show your code where you are facing the problem. Commented Nov 4, 2016 at 5:20
  • 1
    i was expecting more code and error tracebacks rather than theoretical question. Commented Nov 9, 2016 at 10:00
  • 1
    @soupboy when I was younger, I did coding even if I had no definition of "done". I am 40 years old now, and take pyCharm only if I have a definition of done/ready. I tried to find an explanation how to structure a mail which uses both (inline attachments via cid:logo.png and downloadable attachments). I could not find a guideline. That's why I did no coding, that's why this question does not contain a traceback :-) You find code and a nice ascii art structure in the answer ...
    – guettli
    Commented Nov 9, 2016 at 10:50

1 Answer 1

28

I reverse engineered that this structure gets used in practice:

+-------------------------------------------------------+
| multipart/mixed                                       |
|                                                       |
|  +-------------------------------------------------+  |
|  |   multipart/related                             |  |
|  |                                                 |  |
|  |  +-------------------------------------------+  |  |
|  |  | multipart/alternative                     |  |  |
|  |  |                                           |  |  |
|  |  |  +-------------------------------------+  |  |  |
|  |  |  | text can contain [cid:logo.png]     |  |  |  |
|  |  |  +-------------------------------------+  |  |  |
|  |  |                                           |  |  |
|  |  |  +-------------------------------------+  |  |  |
|  |  |  | html can contain src="cid:logo.png" |  |  |  |
|  |  |  +-------------------------------------+  |  |  |
|  |  |                                           |  |  |
|  |  +-------------------------------------------+  |  |
|  |                                                 |  |
|  |  +-------------------------------------------+  |  |
|  |  | image logo.png  "inline" attachment       |  |  |
|  |  +-------------------------------------------+  |  |
|  |                                                 |  |
|  +-------------------------------------------------+  |
|                                                       |
|  +-------------------------------------------------+  |
|  | pdf ("download" attachment, not inline)         |  |
|  +-------------------------------------------------+  |
|                                                       |
+-------------------------------------------------------+

Unfortunately I only found this complicated solution:

from django.core.mail.message import EmailMessage


def create_email(subject='', body='', from_email=None, to=None, bcc=None,
                 connection=None, attachments=[], headers=None,
                 cc=None, reply_to=None, html_body='', html_inline_attachments=[]):
    message = _create_email(subject=subject, body=body, from_email=from_email, to=to, bcc=bcc,
                            connection=connection, headers=headers, cc=cc, reply_to=reply_to,
                            html_body=html_body, html_inline_attachments=html_inline_attachments)

    for attachment in attachments:
        if isinstance(attachment, basestring):
            message.attach_file(attachment)
            continue
        message.attach(attachment)

    return message


def _create_email(subject='', body='', from_email=None, to=None, bcc=None,
                  connection=None, headers=None,
                  cc=None, reply_to=None, html_body='', html_inline_attachments=[]):
    if not (body or html_body):
        raise ValueError('Missing body or html_body!')

    for address, type, name in [
        (from_email, basestring, 'from_email'),
        (to, list, 'to'),
        (cc, list, 'cc'),
        (bcc, list, 'bcc')]:
        if address and not isinstance(address, type):
            raise ValueError('"{}" must be a list! ({})'.format(name, address))

    if body and not html_body:
        if html_inline_attachments:
            raise ValueError('"html_body" is missing!')
        return EmailMessage(subject=subject, body=body, from_email=from_email, to=to, bcc=bcc,
                            connection=connection, headers=headers, cc=cc,
                            reply_to=reply_to)

    if not body:
        body = html_to_text(html_body)
    msg = EmailMessage(subject=subject, from_email=from_email, to=to, bcc=bcc,
                       connection=connection, headers=headers, cc=cc, reply_to=reply_to)
    alternative = MIMEMultipart('alternative')
    alternative.attach(MIMEText(body.encode('utf8'), 'plain', 'utf8'))
    alternative.attach(MIMEText(html_body.encode('utf8'), 'html', 'utf8'))
    related = MIMEMultipart('related')
    related.attach(alternative)
    for inline in html_inline_attachments:
        inline_attachment = msg._create_attachment(os.path.basename(inline), open(inline).read())
        inline_attachment.add_header('Content-Disposition', 'inline')
        inline_attachment.add_header('Content-ID', os.path.basename(inline))
        related.attach(inline_attachment)
    msg.attach(related)
    return msg

If someone has a simpler solution, please let me know :-)

4
  • 3
    This graphical representation helped me understand the structure in better way. Thanks for that. When using SMIME, I observed multipart/mixed is followed by application/pkcs7-signature part and multipart/signed holding both of these. Something like: multipart/signed [ multipart/mixed, application/pkcs7-signature]
    – ramtech
    Commented Jul 21, 2017 at 13:12
  • I'm trying to find documentation on that email structure with no luck. This should be described as part of email protocol but I've never seen this well described. How did you come-up with that email structure? Commented Oct 5, 2022 at 15:25
  • @Mike at 2016 we developed a web based mail user agent as part of this product: tbz-pariv.de/produkte/modwork. We analyzed many mails.
    – guettli
    Commented Oct 6, 2022 at 7:19
  • 1
    This is something unbelievable that we must reverse engineer quite tricky piece of software and it is hard to find detailed documentation on so widely used stuff... Your post is VERY useful, thanks a lot for sharing Commented Oct 6, 2022 at 7:51

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.