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"
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)
* 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