Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use Python’s standard email package to build the message and smtplib to deliver it. If the picture should appear inside the message, create an HTML alternative, give the image a Content-ID, and reference that ID with cid:. If recipients should download the file, add it as a conventional attachment instead. The complete examples below support both choices and use STARTTLS for a typical SMTP submission connection.

Choose how the recipient should receive the image

Goal MIME approach What the recipient sees
Show the picture in the message HTML body plus a related image part and matching Content-ID The mail client can render the image at the position of the <img> element
Offer the original file for download Attachment part with attachment disposition A file in the message’s attachment area
Do both HTML/CID image and a separate attachment Inline preview plus downloadable copy

Inline rendering depends on the recipient’s mail client and its security settings. Always include useful alternative text and a plain-text body. A cid: image is embedded in the message; it is not a remotely hosted image that the client must fetch from a website.

Prerequisites and safe configuration

  • Python 3 with the standard-library email, smtplib, ssl, mimetypes and pathlib modules.
  • An image file readable by the process, such as image.png or photo.jpg.
  • An SMTP submission host, port and authentication method allowed by your mail provider. Hostnames, quotas, OAuth requirements and app-password rules differ by provider; use that provider’s current documentation.
  • Credentials supplied through environment variables or a secret manager, not hard-coded in source control.

The Python email package constructs the message, while smtplib handles the SMTP conversation. Keeping those responsibilities separate makes it easier to test message construction without sending mail.

Send an inline image with HTML and Content-ID

This runnable script creates a plain-text fallback, an HTML alternative and a related image part. The value inside cid: is the Content-ID without angle brackets; the MIME header receives the bracketed ID generated by make_msgid().

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
Lexar D40E 128GB Dual USB 3.2 Gen 1 Type-C Jump Drive, Champagne Silver
  • USB-C 2-in-1 storage OTG: The Lexar JumpDrive Dual Drive D40E features USB Type-A and Type-C connectors in a slim, portable form factor for easy device compatibility
  • Transfer speeds up to 100MB/s: Based on internal testing, performance may vary depending upon the host device, interface, and usage conditions. 1MB=1,000,000 bytes
  • Plug and Play: Widely compatible with USB Type-C smartphones, tablets, laptops, Macs, and traditional Type-A devices, no software installation required. The 360° swivel design allows for easy switching between connectors without the hassle of losing a cap
  • Durable & Compact: The Lexar D40E USB memory stick features a metal enclosure, withstands temperatures from 0° to 50° C (32°F to 122°F), and is lightweight at 26g with dimensions of 70.4 x 16.9 x 11.7mm
  • Security & Warranty: Securely protects files using an advanced security software solution with 256-bit AES encryption. Backed by a Lexar 3-year limited warranty
from email.message import EmailMessage
from email.utils import make_msgid
from pathlib import Path
import mimetypes
import os
import smtplib
import ssl

sender = os.environ["EMAIL_SENDER"]
recipient = os.environ["EMAIL_RECIPIENT"]
image_path = Path(os.environ.get("IMAGE_PATH", "image.png"))

# Generate the ID before writing the HTML body.
image_cid = make_msgid()

msg = EmailMessage()
msg["Subject"] = "An image from Python"
msg["From"] = sender
msg["To"] = recipient
msg.set_content(
    "This message contains an image. If your mail client does not display "
    "HTML, the image is also attached."
)
msg.add_alternative(
    f"""<html><body>
<p>Here is the image:</p>
<img src="cid:{image_cid[1:-1]}" alt="Image sent by Python">
</body></html>""",
    subtype="html",
)

# The last payload is the HTML alternative.
html_part = msg.get_payload()[-1]
image_bytes = image_path.read_bytes()
mime_type, _ = mimetypes.guess_type(image_path.name)
if mime_type is None or not mime_type.startswith("image/"):
    raise ValueError(f"Could not determine an image MIME type for {image_path}")
maintype, subtype = mime_type.split("/", 1)
html_part.add_related(
    image_bytes,
    maintype=maintype,
    subtype=subtype,
    cid=image_cid,
    filename=image_path.name,
)

smtp_host = os.environ["SMTP_HOST"]
smtp_port = int(os.environ.get("SMTP_PORT", "587"))
password = os.environ["SMTP_PASSWORD"]
context = ssl.create_default_context()

with smtplib.SMTP(smtp_host, smtp_port, timeout=90) as smtp:
    smtp.ehlo()
    smtp.starttls(context=context)
    smtp.ehlo()  # Required after the TLS upgrade before authentication.
    smtp.login(sender, password)
    smtp.send_message(msg)

print("Message submitted")

Install no third-party package for this example. Set the variables before running (the exact values come from your provider):

export EMAIL_SENDER='[email protected]'
export EMAIL_RECIPIENT='[email protected]'
export SMTP_HOST='smtp.example.com'
export SMTP_PORT='587'
export SMTP_PASSWORD='use-a-provider-approved-secret'
export IMAGE_PATH='image.png'
python send_image.py

Why the Content-ID details matter

make_msgid() returns an ID in angle brackets, suitable for the MIME Content-ID header. HTML uses the same identifier without those brackets: cid:identifier. A mismatch produces a broken-image icon even though the message was accepted by SMTP.

add_related() creates or appends to a multipart/related section. With no attachment disposition, the image is treated as an inline resource. The HTML part and its related image must be kept together; adding the image to the top-level message can prevent clients from resolving the CID.

Rank #2
SANDISK 128GB Ultra Flair, USB-A Flash Drive, Up to 150MB/s Read Speeds
  • High-speed USB 3.0 performance of up to 150MB/s(1) [(1) Write to drive up to 15x faster than standard USB 2.0 drives (4MB/s); varies by drive capacity. Up to 150MB/s read speed. USB 3.0 port required. Based on internal testing; performance may be lower depending on host device, usage conditions, and other factors; 1MB=1,000,000 bytes]
  • Transfer a full-length movie in less than 30 seconds(2) [(2) Based on 1.2GB MPEG-4 video transfer with USB 3.0 host device. Results may vary based on host device, file attributes and other factors]
  • Transfer to drive up to 15 times faster than standard USB 2.0 drives(1)
  • Sleek, durable metal casing
  • Easy-to-use password protection for your private files(3) [(3)Password protection uses 128-bit AES encryption and is supported by Windows 7, Windows 8, Windows 10, and Mac OS X v10.9 plus; Software download required for Mac, visit the SanDisk SecureAccess support page]

Send the image as a downloadable attachment

For an attachment, you do not need HTML or a Content-ID. Build a normal text message and call add_attachment(). The method marks the part as an attachment by default.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
from email.message import EmailMessage
from pathlib import Path
import mimetypes
import os
import smtplib
import ssl

sender = os.environ["EMAIL_SENDER"]
recipient = os.environ["EMAIL_RECIPIENT"]
image_path = Path("image.jpg")

msg = EmailMessage()
msg["Subject"] = "Your requested photo"
msg["From"] = sender
msg["To"] = recipient
msg.set_content("The requested image is attached.")

mime_type, _ = mimetypes.guess_type(image_path.name)
if mime_type is None or not mime_type.startswith("image/"):
    raise ValueError("The file does not have a recognized image MIME type")
maintype, subtype = mime_type.split("/", 1)
msg.add_attachment(
    image_path.read_bytes(),
    maintype=maintype,
    subtype=subtype,
    filename=image_path.name,
)

context = ssl.create_default_context()
with smtplib.SMTP(os.environ["SMTP_HOST"], int(os.environ.get("SMTP_PORT", "587")), timeout=90) as smtp:
    smtp.ehlo()
    smtp.starttls(context=context)
    smtp.ehlo()
    smtp.login(sender, os.environ["SMTP_PASSWORD"])
    smtp.send_message(msg)

Use the attachment route when the original bytes matter more than an in-message preview, when the recipient may use a text-only client, or when your HTML design is not needed. To provide both experiences, retain the inline script and add a second msg.add_attachment(...) call to the top-level message after constructing the alternatives; test the resulting MIME tree with the clients your audience uses.

SMTP security, authentication and provider differences

STARTTLS on a submission port

The first script connects with smtplib.SMTP, advertises capabilities with ehlo(), upgrades the connection with starttls(context=ssl.create_default_context()), then issues ehlo() again. Commands sent after the upgrade are encrypted. This sequence follows the documented smtplib behavior.

Rank #3
2 Pack 64GB USB Flash Drive USB 2.0 Thumb Drives Jump Drive Fold Storage Memory Stick Swivel Design - Black
  • What You Get - 2 pack 64GB genuine USB 2.0 flash drives, 12-month warranty and lifetime friendly customer service
  • Great for All Ages and Purposes – the thumb drives are suitable for storing digital data for school, business or daily usage. Apply to data storage of music, photos, movies and other files
  • Easy to Use - Plug and play USB memory stick, no need to install any software. Support Windows 7 / 8 / 10 / Vista / XP / Unix / 2000 / ME / NT Linux and Mac OS, compatible with USB 2.0 and 1.1 ports
  • Convenient Design - 360°metal swivel cap with matt surface and ring designed zip drive can protect USB connector, avoid to leave your fingerprint and easily attach to your key chain to avoid from losing and for easy carrying
  • Brand Yourself - Brand the flash drive with your company's name and provide company's overview, policies, etc. to the newly joined employees or your customers

Implicit TLS or another provider mode

Some providers document an implicit-TLS endpoint rather than STARTTLS. In that case, use the provider’s specified secure connection class and port instead of blindly applying the example. Do not disable certificate verification to “fix” a connection error; correct the hostname, port or trust configuration.

Login policy

smtp.login() tries authentication methods advertised by the server. A rejected login can mean incorrect credentials, a disallowed password, a required app password, OAuth-only access, an unverified sender, or an account policy that blocks SMTP. Follow the provider’s current SMTP/OAuth instructions rather than assuming a normal web-account password will work.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Image format, size and rendering considerations

  • Use the actual subtype: image/png, image/jpeg, image/gif and so on. The script detects it from the filename with mimetypes.guess_type(); an unusual extension may require an explicit mapping.
  • Read binary data with read_bytes() or open(..., "rb"). Text mode can corrupt an image.
  • Large images increase MIME size and may hit provider message limits. Resize or compress before sending when the recipient does not need the original resolution.
  • Keep meaningful alt text. Some clients block images or show only the plain-text alternative.
  • Do not promise identical layout across clients. HTML/CSS support, automatic image blocking, CID handling and attachment presentation vary; send a test to the clients your users actually use.
  • Never put secrets, tokens or private data in image URLs or filenames. A CID image is embedded, but the message itself can still be forwarded or archived.

Common failures and fixes

Symptom Likely cause Fix
Broken image icon The HTML CID does not match the image part, or the image was added outside the related section. Use one make_msgid() value, put image_cid[1:-1] in HTML, pass the full value to add_related(cid=...), and call it on the HTML part.
“Could not determine MIME type” The filename extension is unknown or missing. Use a recognized extension or set the known maintype and subtype explicitly after verifying the file format.
Authentication rejected Wrong credentials, blocked basic password, missing app password/OAuth, or account policy. Check the provider’s current authentication requirements and confirm the sender is permitted.
TLS or certificate error Wrong host/port, implicit TLS used where STARTTLS is expected, or an invalid certificate chain. Copy the provider’s documented endpoint, use the matching SMTP class, and keep the default SSL context.
Message accepted but never arrives Provider quota, spam filtering, sender verification or recipient-side filtering. Inspect SMTP response logs and provider activity, verify addresses, and test a small message without the image.
Script hangs Network or server delay. Set a finite SMTP timeout, as shown, and log which stage (connect, TLS, login or send) is slow.

Testing and operational practices

  1. Construct the message in a local test and inspect msg.as_string() without sending secrets to a log.
  2. Send first to an account you control. Check an HTML-capable client, a text-only view and the attachment list.
  3. Test the exact image formats and sizes your application accepts.
  4. Keep SMTP connections short for occasional mail. For batches, follow the provider’s connection, rate and quota guidance; avoid opening an unbounded number of simultaneous sessions.
  5. Catch and record exception types such as SMTPAuthenticationError, SMTPConnectError and SMTPServerDisconnected without recording passwords or full message contents.

The standard library does not provide delivery tracking. An accepted SMTP submission means the server accepted responsibility for processing, not that the recipient read the message. Provider logs and bounce messages are the appropriate source for later delivery diagnosis.

Rank #4
SIMMAX 32GB Memory Stick USB 2.0 Flash Drives Swivel Thumb Drive Pen Drive (32GB Purple)
  • GOOD VALUE PACKAGE - 1 Pack 32GB Memory Stick USB 2.0 Flash Drives with great cost performance and high quality.
  • BIG CAPACITY - The available capacity: 29.10GB-29.8GB, You can save the data of movies, music, photos, designs, programs, manuals, handouts in a high speed.Good performance in digital data storing, transferring and sharing with families, friends, workmates, clients and machines.
  • EASY TO USE & PLUG AND WORK - Support windows 7 / 8 / 10 / Vista / XP / 2000 / ME / NT Linux and Mac OS, Compatible with USB2.0 and below.
  • TWISTTURN DESIGN & EASY CARRY - The metal clip rotates 360° round the ABS plastic body which with rubber oil skin feeling finish. The capless design can avoid lossing of cap, and providing efficient protection to the USB port.
  • WARRANTY & SUPPORT - SIMMAX logo is laser printed on the USB connector surface, our products are of good quality and we promise that any problem about the product within one year since you buy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If the image you want to email is a webpage capture, you can generate it without writing browser automation. ScreenshotNeo is a screenshot API and MCP server. One GET request returns PNG, JPEG, WebP or PDF; it accepts consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets before capture. Bot checks, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing result.

After saving the returned bytes, pass the file path to either Python email example above. The API call itself can be made with cURL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python and Node.js equivalents are useful when capture and email are in the same job:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());

See the ScreenshotNeo documentation for request options. Its MCP server includes take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients. Every plan includes all features; 1,000 screenshots per month are free with no card, and paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to try it.

Best Value
Sale
IMEASON Swivel Design 16GB USB Flash Drive with Keychain, USB 2.0 Portable Thumb Drive Memory Stick, FAT32 Format Flashdrive for Data Storage, Photos, Music, Files (Black, 16 GB)
  • 【16GB Flash Drive】USB flash drives with 16GB capacity, meet your needs of daily use on work, school, home and travelling for photos, music, videos, files storage and transfer. IMEASON thumb drives can be used to store different files, easy to data backup.
  • 【Metal Swivel Cap Design】USB thumb drive is metal swivel cover provides extra protection for the usb thumbdrive connector, no usb drive cap to lose; keychain design makes it easier to carry without worrying lose it.
  • 【Wide Compatibility】USB drive supports Windows 7/8/10/11 / Vista / XP / Unix / 2000 / ME / NT Linux and Mac OS, also Supports USB 2.0 and 1.1 ports. USB Stick support TV, desktop, notebook computer, car, audio and other device. The USB Memory Stick is your great data storage and transfer companion with traveling and working.
  • 【Easy to use】usb memory stick is plug and play without any software installation. Just simply plug the Flashdrive into the port of your USB-compatible devices such as computer, laptop to start data storage or transmission.
  • 【What You Get】16 GB USB Flash Drive Thumb Drive, The default format of the usb storage flash drive is FAT32.

Frequently asked questions

Can I send an image without HTML?

Yes. Use add_attachment() with a plain-text message. HTML and Content-ID are needed only for an image displayed inside the message.

Why remove the angle brackets from the CID in HTML?

MIME headers use the bracketed Content-ID syntax, while a cid: URL uses the identifier itself. The two forms refer to the same ID.

Do I need Pillow or another image library?

No. For reading and attaching existing files, Python’s standard library is sufficient. An image library is only needed if your application must resize, convert or edit pixels.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently Asked Questions

Can I send an image without HTML?

Yes. Use add_attachment() with a plain-text message. HTML and Content-ID are needed only for an image displayed inside the message.

Why remove the angle brackets from the CID in HTML?

MIME headers use the bracketed Content-ID syntax, while a cid: URL uses the identifier itself. The two forms refer to the same ID.

Do I need Pillow or another image library?

No. For reading and attaching existing files, Python’s standard library is sufficient. An image library is only needed if your application must resize, convert or edit pixels.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.