Search code examples
emailsmtpmimesmtplibnewsletter

Send template e-mail (.msg) through SMTP with inline images


I have an e-mail (newsletter) as .msg file which I would like to send through SMTP via Python.

I have trouble embedding the images of the original e-mail in the e-mail which I sent via SMTP. They keep appearing as crosses (X) in the e-mail. E-mails are received, though simply without correctly displayed images.

In the content I do see <img src="cid:[email protected]" ... />

I have the following example

import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
import extract_msg

# Function to extract email content and attachments from .msg file
def load_msg_file(file_path):
    msg = extract_msg.Message(file_path)
    
    # Extract components
    try:
        msg_body = msg.body or ""  # Plain text content
    except UnicodeDecodeError as e:
        print(f"Failed to decode plain text content: {e}")
        msg_body = ""
    
    try:
        msg_html = msg.htmlBody or b""  # HTML content as bytes
    except UnicodeDecodeError as e:
        print(f"Failed to decode HTML content: {e}")
        msg_html = b""

    # Decode HTML content to string
    if isinstance(msg_html, bytes):
        msg_html = msg_html.decode('utf-8', errors='ignore')

    msg_from = msg.sender
    msg_subject = msg.subject
    inline_images = {}

    # Extract attachments (specifically for inline images)
    for attachment in msg.attachments:
        file_name = attachment.longFilename or attachment.shortFilename
        if file_name and f'cid:{file_name}' in msg_html:
            # If the image filename is referenced in the HTML content
            inline_images[file_name] = attachment.data
            print(f"Found inline image: {file_name}")

    return msg_from, msg_subject, msg_body, msg_html, inline_images

# SMTP configuration
SMTP_HOST = "host"
SMTP_PORT = 587
SMTP_USERNAME = "username"
SMTP_PASSWORD = "password"

# Load the .msg file
msg_file_path = r"Newsletter.msg"
from_email, subject, plain_text_content, html_content, inline_images = load_msg_file(msg_file_path)

# Decode HTML content if necessary
if isinstance(html_content, bytes):
    html_content = html_content.decode('utf-8', errors='ignore')

# Ensure HTML contains references like <img src="cid:image1.png">
print("HTML Content:\n", html_content)

# Recipient's email
to_email = "[email protected]"

# Create the email
email = MIMEMultipart("related")
email['From'] = "[email protected]"
email['To'] = to_email
email['Subject'] = subject

# Create alternative part for plain text and HTML
alt_part = MIMEMultipart("alternative")

if plain_text_content:
    alt_part.attach(MIMEText(plain_text_content, 'plain'))

if html_content:
    alt_part.attach(MIMEText(html_content, 'html'))

email.attach(alt_part)

# Attach inline images as embedded resources
for file_name, image_data in inline_images.items():
    img_part = MIMEImage(image_data)
    img_part.add_header('Content-ID', f"<{file_name}>")  # Set the Content-ID as 'cid:<file_name>'
    img_part.add_header('Content-Disposition', 'inline')
    email.attach(img_part)

# Send the email
try:
    with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as server:
        server.starttls()
        server.login(SMTP_USERNAME, SMTP_PASSWORD)
        server.sendmail(from_email, to_email, email.as_string())
        print("Email sent successfully!")
except Exception as e:
    print(f"Failed to send email: {e}")

--edit-- Working code with .eml instead of .msg.

import smtplib
from email import policy
from email.parser import BytesParser
from email.mime.text import MIMEText
from email.utils import formatdate
from datetime import datetime, timezone

# SMTP Configuration
smtp_host = "host"
smtp_port = 587  # Alternative: 2525
username = "username"
api_key = "key"

# Path to the .eml file
eml_file_path = r"C:\mail.eml"  # Replace with your .eml file's path

# Current date
current_date = formatdate(timeval=None, localtime=False, usegmt=True)

from_email = [email protected]
to_email = [email protected]

# Read and parse the .eml file
try:
    # Read and parse the .eml file
    with open(eml_file_path, 'rb') as eml_file:
        msg = BytesParser(policy=policy.default).parse(eml_file)

    # Replace headers (if needed)
    msg.replace_header('Date',current_date)
    msg.replace_header('From',from_email)
    msg.replace_header('To',to_email)

    # Send email via SMTP
    with smtplib.SMTP(smtp_host, smtp_port) as server:
        server.starttls()  # Upgrade the connection to secure
        server.login(username, api_key)  # Log in to the SMTP server
        server.send_message(msg, from_addr=from_email, to_addrs=[to_email])  # Send the email
        print("Email sent successfully.")
        
except Exception as e:
    print(f"Error sending email: {e}")

Solution

  • The problem is to convert .msg to .eml, a workaround is to use a library that does it directly like this one then send the converted .eml.

    This might be the easiest fix. If you want to still load a .msg and build an .eml file from it in your custom code, you have to fix your code for including correctly attachments and linking them correctly in the html (there's missing fields,...)

    Note : .msg files have a size limit of 14MB so .eml could be better to use instead anyways.