The email module handles creating and parsing email messages. It's the foundation for working with email in Python—building messages, parsing received mail, and handling attachments.

Create Simple Email

from email.message import EmailMessage
 
msg = EmailMessage()
msg['Subject'] = 'Hello from Python'
msg['From'] = 'sender@example.com'
msg['To'] = 'recipient@example.com'
msg.set_content('This is the email body.')
 
print(msg)

HTML Email

from email.message import EmailMessage
 
msg = EmailMessage()
msg['Subject'] = 'HTML Email'
msg['From'] = 'sender@example.com'
msg['To'] = 'recipient@example.com'
 
# Set HTML content with plain text alternative
msg.set_content('Plain text version')
msg.add_alternative('''
<html>
<body>
    <h1>Hello!</h1>
    <p>This is <b>HTML</b> content.</p>
</body>
</html>
''', subtype='html')
 
print(msg)

Add Attachments

from email.message import EmailMessage
import mimetypes
 
msg = EmailMessage()
msg['Subject'] = 'Email with Attachment'
msg['From'] = 'sender@example.com'
msg['To'] = 'recipient@example.com'
msg.set_content('Please see attached file.')
 
# Add file attachment
filename = 'document.pdf'
with open(filename, 'rb') as f:
    file_data = f.read()
    file_type, _ = mimetypes.guess_type(filename)
    maintype, subtype = file_type.split('/')
    
    msg.add_attachment(
        file_data,
        maintype=maintype,
        subtype=subtype,
        filename=filename
    )

Add Image Attachment

from email.message import EmailMessage
 
msg = EmailMessage()
msg['Subject'] = 'Photo'
msg['From'] = 'sender@example.com'
msg['To'] = 'recipient@example.com'
msg.set_content('Here is the photo.')
 
with open('photo.jpg', 'rb') as f:
    msg.add_attachment(
        f.read(),
        maintype='image',
        subtype='jpeg',
        filename='photo.jpg'
    )

Inline Images in HTML

from email.message import EmailMessage
import uuid
 
msg = EmailMessage()
msg['Subject'] = 'Inline Image'
msg['From'] = 'sender@example.com'
msg['To'] = 'recipient@example.com'
 
# Generate content ID
cid = str(uuid.uuid4())
 
# HTML with inline image reference
html = f'''
<html>
<body>
    <h1>Look at this!</h1>
    <img src="cid:{cid}">
</body>
</html>
'''
 
msg.set_content('Plain text fallback')
msg.add_alternative(html, subtype='html')
 
# Add image with content ID
with open('image.png', 'rb') as f:
    msg.get_payload()[1].add_related(
        f.read(),
        maintype='image',
        subtype='png',
        cid=cid
    )

Parse Email

Read and extract email parts:

from email import message_from_string, message_from_bytes
 
# From string
email_text = '''From: sender@example.com
To: recipient@example.com
Subject: Test Email
 
This is the body.'''
 
msg = message_from_string(email_text)
 
print(msg['From'])     # sender@example.com
print(msg['Subject'])  # Test Email
print(msg.get_payload())  # This is the body.

Parse Multipart Email

from email import message_from_bytes
 
def parse_email(raw_email):
    """Extract parts from multipart email."""
    msg = message_from_bytes(raw_email)
    
    result = {
        'from': msg['From'],
        'to': msg['To'],
        'subject': msg['Subject'],
        'body': None,
        'html': None,
        'attachments': []
    }
    
    if msg.is_multipart():
        for part in msg.walk():
            content_type = part.get_content_type()
            disposition = str(part.get('Content-Disposition', ''))
            
            if content_type == 'text/plain' and 'attachment' not in disposition:
                result['body'] = part.get_payload(decode=True).decode()
            elif content_type == 'text/html' and 'attachment' not in disposition:
                result['html'] = part.get_payload(decode=True).decode()
            elif 'attachment' in disposition:
                result['attachments'].append({
                    'filename': part.get_filename(),
                    'content_type': content_type,
                    'data': part.get_payload(decode=True)
                })
    else:
        result['body'] = msg.get_payload(decode=True).decode()
    
    return result

Save Attachments

from email import message_from_bytes
import os
 
def save_attachments(raw_email, output_dir):
    """Save all attachments from email."""
    msg = message_from_bytes(raw_email)
    
    saved = []
    for part in msg.walk():
        if part.get_content_disposition() == 'attachment':
            filename = part.get_filename()
            if filename:
                filepath = os.path.join(output_dir, filename)
                with open(filepath, 'wb') as f:
                    f.write(part.get_payload(decode=True))
                saved.append(filepath)
    
    return saved

Handle Encodings

from email.header import decode_header
 
def decode_subject(msg):
    """Decode email subject handling various encodings."""
    subject = msg['Subject']
    if subject:
        decoded_parts = decode_header(subject)
        decoded_subject = ''
        for part, encoding in decoded_parts:
            if isinstance(part, bytes):
                decoded_subject += part.decode(encoding or 'utf-8')
            else:
                decoded_subject += part
        return decoded_subject
    return ''

Build with MIMEMultipart (Legacy)

Older API still works:

from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
 
msg = MIMEMultipart()
msg['Subject'] = 'Legacy Style'
msg['From'] = 'sender@example.com'
msg['To'] = 'recipient@example.com'
 
# Attach text
msg.attach(MIMEText('Body text', 'plain'))
 
# Attach file
with open('file.pdf', 'rb') as f:
    part = MIMEBase('application', 'octet-stream')
    part.set_payload(f.read())
    encoders.encode_base64(part)
    part.add_header('Content-Disposition', 'attachment', filename='file.pdf')
    msg.attach(part)

Send via SMTP

from email.message import EmailMessage
import smtplib
 
msg = EmailMessage()
msg['Subject'] = 'Test'
msg['From'] = 'sender@example.com'
msg['To'] = 'recipient@example.com'
msg.set_content('Hello!')
 
# Send
with smtplib.SMTP('smtp.example.com', 587) as server:
    server.starttls()
    server.login('user', 'password')
    server.send_message(msg)

Email Validation

Basic email format check:

import re
 
def is_valid_email(email):
    """Basic email format validation."""
    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    return bool(re.match(pattern, email))
 
print(is_valid_email('user@example.com'))  # True
print(is_valid_email('invalid'))  # False

Multiple Recipients

from email.message import EmailMessage
 
msg = EmailMessage()
msg['Subject'] = 'Team Update'
msg['From'] = 'sender@example.com'
msg['To'] = 'alice@example.com, bob@example.com'
msg['Cc'] = 'manager@example.com'
msg['Bcc'] = 'archive@example.com'
msg.set_content('Update for the team.')

When to Use email Module

Use email module when:

  • Building email messages with attachments
  • Parsing received emails
  • Processing email files (.eml)
  • Custom email handling

Use higher-level libraries when:

  • Simple sending only → smtplib alone
  • Full email client → imaplib + email
  • Production apps → consider yagmail, emails

The email module handles the complexity of MIME—use it whenever you need more than plain text.

React to this post: