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] = messageFormat-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.
Recommended practices
- Prefer Maildir for concurrent writing.
- Lock formats that require it.
- Call
flush()andclose(). - Back up before batch modifications.
- Parse binary messages with an explicit policy.
- Validate counts, flags, and hashes after migration.
- Bound all untrusted content processing.
Related guides
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.







