Python mailbox: Local Email Stores

Published on: August 12, 2026
Reading time: 5 minutes
Document and inbox representing local email stores with Python mailbox

The mailbox module manipulates email mailboxes stored on disk. It supports Maildir, mbox, MH, Babyl, and MMDF and presents a dictionary-like interface whose keys identify messages. It is useful for migration tools, backups, local filters, search indexes, forensic utilities, and integrations with desktop mail clients.

The module does not download messages through IMAP or send mail through SMTP. It works with local storage formats. Combine it with the email package to parse headers, MIME parts, attachments, and message bodies.

Choose the storage format

Maildir stores each message in a separate file under tmp, new, and cur. This design tolerates unrelated processes better and generally does not require a global mailbox lock. mbox and MMDF store many messages in one file and require careful locking for modifications.

For a new system that needs concurrent writers, Maildir is usually safer. When reading an existing archive, keep the source format and follow its locking rules.

Open a Maildir

import mailbox

box = mailbox.Maildir("mail", create=True)
print(len(box))

for message in box:
    print(message.get("Subject"))

Default iteration returns message representations, not keys. Each retrieval builds a fresh object from the current mailbox state. Mutating the object does not update the store until it is assigned back to its key.

Iterate over keys

for key in box.iterkeys():
    message = box.get_message(key)
    print(key, message.get("From"))

Keys are meaningful only to that mailbox instance and format. Operations such as MH.pack() may invalidate previously issued keys. Do not use them as permanent global identifiers.

Add a message

from email.message import EmailMessage

message = EmailMessage()
message["From"] = "alice@example.com"
message["To"] = "bob@example.com"
message["Subject"] = "Report"
message.set_content("Report body")

key = box.add(message)

add() accepts mailbox messages, ordinary email.message.Message objects, strings, bytes, and binary file-like objects. The content is copied; the mailbox does not keep a live reference to the supplied object.

Replace a message

message = box.get_message(key)
message.replace_header("Subject", "Revised report")
box[key] = message

Format-specific metadata, such as flags, may be preserved or converted according to the mailbox subclass. Review the subclass rules before migrating state.

Remove messages

box.remove(key)
# del box[key]
# box.discard(key)

remove() and del raise KeyError if the key is missing. discard() ignores a missing key, which can be preferable when another process may remove messages concurrently.

Lock single-file mailboxes

Acquire the mailbox lock before reading and changing mbox, MMDF, and other formats that require it.

box = mailbox.mbox("archive.mbox")
box.lock()
try:
    for key, message in box.iteritems():
        if message.get("Subject") == "Delete":
            box.discard(key)
    box.flush()
finally:
    box.unlock()
    box.close()

Without locking, two processes can overwrite changes, lose messages, or corrupt the entire file. An unavailable lock may raise ExternalClashError.

Maildir concurrency

Maildir avoids a global lock because messages are separate files. The documentation still warns that writing from multiple threads can generate filename collisions unless the application coordinates writers to the same mailbox.

Use an application-level lock for local threads and test behavior on the actual filesystem, especially network-mounted storage.

Maildir flags

Python 3.13 added efficient mailbox-level methods for reading and changing flags without opening the full message.

flags = box.get_flags(key)
box.add_flag(key, "S")
box.remove_flag(key, "F")
box.set_flags(key, "RS")

A previously loaded MaildirMessage object is not automatically synchronized with mailbox-level flag changes. Reload it before making further decisions.

Maildir info fields

get_info() and set_info(), also added in Python 3.13, access the info portion of a Maildir filename. Use flag methods for standard states such as seen, replied, or flagged.

Maildir folders

print(box.list_folders())
archive = box.add_folder("Archive.2026")
subbox = box.get_folder("Archive.2026")

Courier-style folders use leading dots on disk and dot-separated logical levels. Folder layout has its own interoperability conventions.

Clean temporary deliveries

Maildir.clean() removes old temporary files from tmp according to Maildir conventions. Run it carefully on unstable storage and keep backups when recovery matters.

Read bytes, text, or a file

raw = box.get_bytes(key)
text = box.get_string(key)

with box.get_file(key) as file:
    first_line = file.readline()

get_bytes() is best for faithful processing. get_string() passes through the email package and produces a seven-bit-clean representation. Use a binary parser with an explicit email policy for modern applications.

Custom message factories

from email import policy
from email.parser import BytesParser

def factory(file):
    return BytesParser(policy=policy.default).parse(file)

box = mailbox.Maildir("mail", factory=factory)

A factory can return modern email objects or lightweight metadata, reducing memory when a full format-specific message is unnecessary.

mbox and the From line

mbox separates messages with lines beginning with From . Body lines that begin the same way are escaped when stored. Multiple incompatible mbox variants exist, so migrations require testing.

mbox versions of get_bytes(), get_file(), and get_string() accept a from_ option controlling the Unix From line.

MH sequences

MH stores one message per file and supports named sequences.

box = mailbox.MH("mh")
sequences = box.get_sequences()
sequences["important"] = ["1", "3"]
box.set_sequences(sequences)

pack() renumbers messages to remove gaps and invalidates issued keys. Never continue using old keys afterward.

Migrate between formats

source = mailbox.mbox("source.mbox")
target = mailbox.Maildir("target", create=True)

source.lock()
try:
    for message in source:
        target.add(message)
finally:
    source.unlock()
    source.close()
    target.close()

Before migration, copy the source, count messages, record hashes, and decide how format-specific flags should be mapped. Validate attachments and open the result with an independent client.

Modification during iteration

Messages added after an iterator is created are not seen. Messages removed before iteration reaches them are skipped. Concurrent processes can still invalidate individual keys.

Untrusted message content

Email may contain malformed headers, deep MIME trees, huge attachments, dangerous filenames, and active HTML. Never execute attachments or render HTML without sanitization. Set limits for message size, part count, nesting, and decompression.

Backups and flush

Before destructive mbox changes, create a backup and verify free disk space. flush() writes pending changes, but it is not a substitute for a recovery plan.

Maildir operations are more isolated, yet large migrations still need checkpoints and restartable processing.

Export message metadata

import json
import mailbox

box = mailbox.Maildir("mail")
records = []
for key in box.iterkeys():
    msg = box.get_message(key)
    records.append({
        "key": str(key),
        "from": msg.get("From"),
        "to": msg.get("To"),
        "subject": msg.get("Subject"),
        "date": msg.get("Date"),
        "flags": msg.get_flags(),
    })

with open("index.json", "w", encoding="utf-8") as file:
    json.dump(records, file, ensure_ascii=False, indent=2)

Headers are untrusted and may repeat or contain invalid dates. Normalize them only for derived indexes and preserve raw data.

Common mistakes

  • Modifying mbox without a lock.
  • Expecting an edited Message object to update storage automatically.
  • Treating keys as permanent IDs.
  • Closing a mailbox while a returned file object is still needed.
  • Confusing local mailbox access with IMAP.
  • Ignoring mbox variants.
  • Processing attachments without limits.
  • Prefer Maildir for concurrent writing.
  • Lock formats that require it.
  • Call flush() and close().
  • Back up before batch modifications.
  • Parse binary messages with an explicit policy.
  • Validate counts, flags, and hashes after migration.
  • Bound all untrusted content processing.

Continue with Python quopri, Python mimetypes, Python fileinput, Python filecmp, and Python ExitStack.

See the official mailbox documentation and the email package documentation.

Conclusion

mailbox provides a uniform interface over very different local mail formats. Reliable use requires understanding locking, copy semantics, key lifetime, format differences, and untrusted-message risks. For new concurrent stores, Maildir is often the safest foundation.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Text editor representing formatting with Python textwrap
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python textwrap: Format Text

    Learn Python textwrap to wrap, fill, shorten, indent, and dedent text with controlled width, whitespace, long words, and reusable settings.

    Ler mais

    Tempo de leitura: 4 minutos
    10/08/2026
    Folder and magnifying glass representing file-name filters with Python fnmatch
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python fnmatch: Filter File Names

    Learn Python fnmatch to filter file names with shell wildcards, control case sensitivity, exclude patterns, and distinguish glob from regex.

    Ler mais

    Tempo de leitura: 5 minutos
    10/08/2026
    Monitor with binary data representing compact numeric arrays in Python
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python array: Compact Numeric Data

    Learn Python array for compact numeric storage, binary files, byte order, memory views, and safe buffer interoperability.

    Ler mais

    Tempo de leitura: 6 minutos
    10/08/2026
    Color wheel representing RGB, HSV, and HLS conversions with Python colorsys
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python colorsys: RGB, HSV, and HLS

    Learn Python colorsys to convert colors between RGB, HSV, HLS, and YIQ, generate palettes, and avoid scale and precision mistakes.

    Ler mais

    Tempo de leitura: 5 minutos
    09/08/2026
    Configuration icon representing plist files with Python plistlib
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python plistlib: Apple Plist Files

    Learn Python plistlib to read and write XML and binary plist files, validate data, handle dates, bytes, and UIDs safely.

    Ler mais

    Tempo de leitura: 6 minutos
    08/08/2026
    Digital padlock representing host credentials with Python netrc
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python netrc: Credentials by Host

    Learn Python netrc to read credentials by host, validate permissions, handle parse errors, and integrate network clients safely.

    Ler mais

    Tempo de leitura: 6 minutos
    08/08/2026