【Python】How to Send Emails with Attachments (Automatic MIME Type Detection)

目次

Overview

This article explains how to create and send emails with attachments such as PDFs or images using the standard Python library email.message. We will show a versatile implementation that uses the mimetypes module to automatically detect the data format (MIME type) of the attached file.

Specifications (Input/Output)

  • Input:
    • Sender and recipient information, subject, and body text.
    • The file path of the attachment.
  • Output:
    • Generation of an email message object containing the attachment.
    • Execution of the email transfer via an SMTP server.
  • Requirements:
    • An available SMTP server environment (such as Gmail).
    • The attachment file must exist and be readable.

Basic Usage

This is the basic procedure for reading a file in binary mode, identifying its MIME type, and adding it to the email object.

from email.message import EmailMessage
import mimetypes

msg = EmailMessage()
msg.set_content("Sending a file.")

file_path = "report.pdf"

# Guess the MIME type of the file
ctype, encoding = mimetypes.guess_type(file_path)
if ctype is None:
    ctype = "application/octet-stream" # Default if unknown

maintype, subtype = ctype.split("/", 1)

# Read the file and attach it
with open(file_path, "rb") as f:
    file_data = f.read()
    msg.add_attachment(
        file_data,
        maintype=maintype,
        subtype=subtype,
        filename=f.name
    )

Full Code

Below is a complete implementation example that creates an email with an attachment and sends it to a local SMTP server for testing. In production, change the SMTP host settings to your service provider (e.g., Gmail).

import smtplib
import mimetypes
import os
from email.message import EmailMessage

def send_email_with_attachment():
    """
    Demo function to send an email with an attachment.
    """
    # 1. Create email information
    msg = EmailMessage()
    msg["Subject"] = "[Daily Report] Sending Attachment"
    msg["From"] = "myself@example.com"
    msg["To"] = "boss@example.com"
    msg.set_content("Hello.\nPlease find today's report attached.\nThank you.")

    # 2. Process attachment
    # Assume an image file exists in the same directory
    target_file = "monthly_chart.png"
    
    # Check if file exists to avoid errors
    if not os.path.exists(target_file):
        print(f"Error: File '{target_file}' not found.")
        return

    # Detect MIME type
    mime_type, _ = mimetypes.guess_type(target_file)
    if mime_type is None:
        # Treat as general binary data if detection fails
        mime_type = "application/octet-stream"
    
    # Split into type/subtype (e.g., image/png -> main=image, sub=png)
    main_type, sub_type = mime_type.split("/", 1)

    try:
        with open(target_file, "rb") as f:
            file_data = f.read()
            file_name = os.path.basename(target_file)
            
            # Add attachment data to the message
            msg.add_attachment(
                file_data,
                maintype=main_type,
                subtype=sub_type,
                filename=file_name
            )
            print(f"Attached file: {file_name} ({mime_type})")

        # 3. SMTP Transfer
        # Using localhost:1025 for a test server
        # Use smtp.gmail.com:587 for production
        smtp_host = "localhost"
        smtp_port = 1025

        print("Sending email...")
        with smtplib.SMTP(smtp_host, smtp_port) as smtp:
            # smtp.starttls() # Enable if necessary
            # smtp.login("user", "pass") # Enable if necessary
            smtp.send_message(msg)
            
        print("Sent successfully.")

    except Exception as e:
        print(f"An error occurred during sending: {e}")

if __name__ == "__main__":
    # Create a test file for demonstration
    with open("monthly_chart.png", "wb") as f:
        f.write(b"Dummy Image Data")
    
    send_email_with_attachment()
    
    # Clean up
    os.remove("monthly_chart.png")

Customization Points

Specifications for the main methods and modules related to attachment processing.

MIME Type Detection (mimetypes)

SyntaxDescription
mimetypes.guess_type(path)Returns a tuple (type, encoding) based on the file extension. Example: 'document.pdf' -> ('application/pdf', None)

Adding Attachments (EmailMessage)

ParameterDescription
data (1st arg)The actual binary data of the file (the return value of f.read()).
maintypeThe main category of the MIME type (e.g., image, application, text).
subtypeThe subcategory of the MIME type (e.g., jpeg, pdf, plain).
filenameThe filename displayed to the recipient.

Explanation

  • Benefits of mimetypes: You do not need to write complex logic like if ext == '.jpg': ... for every extension. This makes your code more general and clean.
  • application/octet-stream: This is a general type indicating “arbitrary binary data.” If an extension cannot be identified, using this ensures the file is at least treated as a downloadable attachment by the recipient’s client.

Important Notes

  • File Size Limits: Most email servers (like Gmail) have a total size limit (e.g., 25MB) for the entire email including attachments. If you send videos or high-resolution images, the server may reject the email even if your Python code is correct.
  • File Path vs. Filename: It is proper etiquette to pass only the filename (using os.path.basename) to the filename argument instead of the full path. Passing a full path may trigger security warnings or display incorrectly in the recipient’s environment.
  • Binary Mode: Always use rb (Read Binary) mode in the open() function when handling non-text files like images or PDFs.

Advanced Usage

Example of a loop to attach all files in a folder at once.

import os
import mimetypes
from email.message import EmailMessage

def attach_all_files_in_dir(msg, directory):
    """
    Attaches all files within a specified directory to the email.
    """
    for filename in os.listdir(directory):
        path = os.path.join(directory, filename)
        
        # Skip directories
        if not os.path.isfile(path):
            continue
            
        # Detect MIME type
        ctype, encoding = mimetypes.guess_type(path)
        if ctype is None or encoding is not None:
            # Use general type if unknown or compressed
            ctype = "application/octet-stream"
            
        maintype, subtype = ctype.split("/", 1)
        
        with open(path, "rb") as f:
            file_data = f.read()
            msg.add_attachment(
                file_data,
                maintype=maintype,
                subtype=subtype,
                filename=filename
            )
            print(f"Added: {filename}")

# Example Use:
# msg = EmailMessage() ...
# attach_all_files_in_dir(msg, "./documents")

Summary

By using the add_attachment method of the EmailMessage class, you can attach files without worrying about complex MIME structures. Combine it with the mimetypes module to automatically set the correct file format and build a smart email delivery program.

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次