Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
535 views
in Technique[技术] by (71.8m points)

Send e-mail to Gmail with inline image using Python

My objective is to use Python to send an e-mail to a Gmail user that has an inline image. It is not possible to host this image online and then link to it through a href, due to the sensitive nature of the images (data from my work).

I've tried encoding the base64 version into a HTML then sending th is HTML, but this is well known to not work. I then noticed that in Gmail you can drag and drop an image into the send box and it will show up inline in the receiving end. Given this I then tried to send an e-mail from Python with the image as an attachment. This is seen in the below code, but unfortunately the image doesn't show up inline.

My question is then: How to send the image such that it shows up inline?

import smtplib
from email.MIMEMultipart import MIMEMultipart
from email.MIMEBase import MIMEBase
from email.MIMEText import MIMEText
from email import Encoders
import os

gmail_user = "[email protected]"
gmail_pwd = "pass"

to = "[email protected]"
subject = "Report"
text = "Picture report"
attach = 'TESTING.png'

msg = MIMEMultipart()

msg['From'] = gmail_user
msg['To'] = to
msg['Subject'] = subject

msg.attach(MIMEText(text))

part = MIMEBase('application', 'octet-stream')
part.set_payload(open(attach, 'rb').read())
Encoders.encode_base64(part)
part.add_header('Content-Disposition',
   'attachment; filename="%s"' % os.path.basename(attach))
msg.attach(part)

mailServer = smtplib.SMTP("smtp.gmail.com", 587)
mailServer.ehlo()
mailServer.starttls()
mailServer.ehlo()
mailServer.login(gmail_user, gmail_pwd)
mailServer.sendmail(gmail_user, to, msg.as_string())
# Should be mailServer.quit(), but that crashes...
mailServer.close()

When I send the inline image to myself manually this is what the "original email" looks like:

  Content-Type: multipart/related; boundary=047d7bd761fe73e03304e7e02237

--047d7bd761fe73e03304e7e02237
Content-Type: multipart/alternative; boundary=047d7bd761fe73e03004e7e02236

--047d7bd761fe73e03004e7e02236
Content-Type: text/plain; charset=ISO-8859-1

[image: Inline images 1]

--047d7bd761fe73e03004e7e02236
Content-Type: text/html; charset=ISO-8859-1

<div dir="ltr"><img alt="Inline images 1" src="cid:ii_141810ee4ae92ac6" height="400" width="534"><br></div>

--047d7bd761fe73e03004e7e02236--
--047d7bd761fe73e03304e7e02237
Content-Type: image/png; name="Testing.png"
Content-Transfer-Encoding: base64
Content-ID: <ii_141810ee4ae92ac6>
X-Attachment-Id: ii_141810ee4ae92ac6

When I send it to myself through Python as an attachment it is very different:

Content-Type: multipart/mixed; boundary="===============6881579935569047077=="
MIME-Version: 1.0
(.... some stuff deleted here)
--===============6881579935569047077==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit

See attachment for report.
--===============6881579935569047077==
Content-Type: application/octet-stream
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="TESTING.png"
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

It seems that following the gmail email template works:

* multipart/alternative
  - text/plain
  - multipart/related
    + text/html
      <img src="cid:msgid"/>
    + image/png
      Content-ID: <msgid>

Based on the example from email module docs:

#!/usr/bin/env python3
import html
import mimetypes
from email.headerregistry import Address
from email.message import EmailMessage
from email.utils import make_msgid
from pathlib import Path

title = 'Picture report…'
path = Path('TESTING.png')
me = Address("Pepé Le Pew", *gmail_user.rsplit('@', 1))

msg = EmailMessage()
msg['Subject'] = 'Report…'
msg['From'] = me
msg['To'] = [me]
msg.set_content('[image: {title}]'.format(title=title))  # text/plain
cid = make_msgid()[1:-1]  # strip <>    
msg.add_alternative(  # text/html
    '<img src="cid:{cid}" alt="{alt}"/>'
    .format(cid=cid, alt=html.escape(title, quote=True)),
    subtype='html')
maintype, subtype = mimetypes.guess_type(str(path))[0].split('/', 1)
msg.get_payload()[1].add_related(  # image/png
    path.read_bytes(), maintype, subtype, cid="<{cid}>".format(cid=cid))

# save to disk a local copy of the message
Path('outgoing.msg').write_bytes(bytes(msg))

To send msg via gmail:

import smtplib
import ssl

with smtplib.SMTP('smtp.gmail.com', timeout=10) as s:
    s.starttls(context=ssl.create_default_context())
    s.login(gmail_user, gmail_password)
    s.send_message(msg)

Python 2/3 compatible version

* multipart/related
  - multipart/alternative
    + text/plain
    + text/html
      <div dir="ltr"><img src="cid:ii_xyz" alt="..."><br></div>
  - image/jpeg
    Content-ID: <ii_xyz>

Based on Send an HTML email with embedded image and plain text alternate:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import cgi
import uuid
import os
from email.mime.multipart import MIMEMultipart
from email.mime.text      import MIMEText
from email.mime.image     import MIMEImage
from email.header         import Header    

img = dict(title=u'Picture report…', path=u'TESTING.png', cid=str(uuid.uuid4()))

msg = MIMEMultipart('related')
msg['Subject'] = Header(u'Report…', 'utf-8')
msg['From'] = gmail_user
msg['To'] = ", ".join([to])
msg_alternative = MIMEMultipart('alternative')
msg.attach(msg_alternative)
msg_text = MIMEText(u'[image: {title}]'.format(**img), 'plain', 'utf-8')
msg_alternative.attach(msg_text)

msg_html = MIMEText(u'<div dir="ltr">'
                     '<img src="cid:{cid}" alt="{alt}"><br></div>'
                    .format(alt=cgi.escape(img['title'], quote=True), **img),
                    'html', 'utf-8')
msg_alternative.attach(msg_html)

with open(img['path'], 'rb') as file:
    msg_image = MIMEImage(file.read(), name=os.path.basename(img['path']))
    msg.attach(msg_image)
msg_image.add_header('Content-ID', '<{}>'.format(img['cid']))

To send msg via gmail:

import ssl

s = SMTP_SSL('smtp.gmail.com', timeout=10,
             ssl_kwargs=dict(cert_reqs=ssl.CERT_REQUIRED,
                             ssl_version=ssl.PROTOCOL_TLSv1,
                             # http://curl.haxx.se/ca/cacert.pem
                             ca_certs='cacert.pem')) 
s.set_debuglevel(0)
try:
    s.login(gmail_user, gmail_pwd)
    s.sendmail(msg['From'], [to], msg.as_string())
finally:
    s.quit()

SMTP_SSL is optional, you could use starttls method from your question instead:

import smtplib
import socket
import ssl
import sys

class SMTP_SSL(smtplib.SMTP_SSL):
    """Add support for additional ssl options."""
    def __init__(self, host, port=0, **kwargs):
        self.ssl_kwargs = kwargs.pop('ssl_kwargs', {})
        self.ssl_kwargs['keyfile'] = kwargs.pop('keyfile', None)
        self.ssl_kwargs['certfile'] = kwargs.pop('certfile', None)
        smtplib.SMTP_SSL.__init__(self, host, port, **kwargs)

    def _get_socket(self, host, port, timeout):
        if self.debuglevel > 0:
            print>>sys.stderr, 'connect:', (host, port)
        new_socket = socket.create_connection((host, port), timeout)
        new_socket = ssl.wrap_socket(new_socket, **self.ssl_kwargs)
        self.file = getattr(smtplib, 'SSLFakeFile', lambda x: None)(new_socket)
        return new_socket

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...