Gumdrop provides an abstraction layer for mailbox access that decouples mail servers from storage implementation details. The IMAP, POP3, and SMTP servers can all access mailboxes through this API, allowing the storage backend to be changed or extended without modifying the protocol implementations.
The mailbox abstraction provides several important benefits:
ReadableByteChannel, consistent with Gumdrop's non-blocking
architecture and enabling zero-copy transfers where possibleThe abstraction is organised into three tiers:
Mailbox names may contain Unicode characters (e.g., "Données", "日本語") and special characters that are problematic on certain filesystems. Gumdrop uses a modified Quoted-Printable encoding based on RFC 2047 to ensure mailbox names are safely stored on all platforms.
The MailboxNameCodec encodes characters as =XX hex
sequences (where XX are uppercase hex digits) for:
/ and \: * ? " < > |= itselfExamples:
"Données/été" → "Donn=C3=A9es=2F=C3=A9t=C3=A9" "Reports:2025" → "Reports=3A2025" "日本語" → "=E6=97=A5=E6=9C=AC=E8=AA=9E"
The encoding produces output containing only safe characters:
A-Z a-z 0-9 . _ - =. This is compatible with:
/ and null are valid;
both are encoded\ / : * ? " < > |
are all encoded; NTFS reserved names (CON, PRN, etc.) are rare in mailbox names: is encoded for HFS+ legacy
compatibility
Unlike full RFC 2047 which uses the =?charset?Q?...?= wrapper format,
this encoding omits the wrapper because the ? character is forbidden
in Windows filenames. The encoding is always UTF-8.
The factory interface creates store instances. Each authenticated session receives its own store to ensure thread safety:
public interface MailboxFactory {
MailboxStore createStore();
}
Factories are configured in gumdroprc and referenced by servers
that require mailbox access.
A store represents a user's complete mail hierarchy. After authentication, the server opens the store for the user:
MailboxStore store = factory.createStore(); store.open(username);
The store provides:
listMailboxes() with wildcard patterns
supporting IMAP's * and % wildcardssubscribe(), unsubscribe(),
listSubscribed() for IMAP folder subscriptionscreateMailbox(),
deleteMailbox(), renameMailbox()openMailbox(name, readOnly)/ or .A mailbox is a single folder containing messages. For POP3, only INBOX is used; IMAP accesses the full hierarchy.
Message operations:
getMessageCount(),
getMessageList(), getMessage(int)getMessageContent(int) returns a
ReadableByteChannel for streaminggetMessageTop(int, lines) for POP3
header + body line retrievaldeleteMessage(int), isDeleted(int),
undeleteAll(), expunge()getUniqueId(int) for persistent message
identification (POP3 UIDL, IMAP UID)IMAP-specific operations:
getFlags(), setFlags(),
replaceFlags() for \Seen, \Answered, \Flagged, \Deleted, \DraftstartAppendMessage(),
appendMessageContent(), endAppendMessage()copyMessages(), moveMessages()search(SearchCriteria) with extensible criteriagetUidValidity(), getUidNext()Message metadata without loading content:
getMessageNumber() - sequence number (1-based, may change on expunge)getSize() - message size in octetsgetUniqueId() - persistent identifier
Mailbox factories are configured in gumdroprc and referenced by
servers:
<!-- Mbox mailbox factory -->
<mailbox-factory id="mboxFactory"
class="org.bluezoo.gumdrop.mailbox.mbox.MboxMailboxFactory">
<property name="base-directory">/var/mail</property>
<property name="extension">.mbox</property>
</mailbox-factory>
<!-- Maildir mailbox factory -->
<mailbox-factory id="maildirFactory"
class="org.bluezoo.gumdrop.mailbox.maildir.MaildirMailboxFactory">
<property name="base-directory">/var/mail</property>
</mailbox-factory>
<!-- IMAP service using Maildir -->
<service class="org.bluezoo.gumdrop.imap.DefaultIMAPService">
<property name="mailbox-factory" ref="#maildirFactory"/>
<property name="realm" ref="#mailRealm"/>
<listener class="org.bluezoo.gumdrop.imap.IMAPListener" port="143"/>
<listener class="org.bluezoo.gumdrop.imap.IMAPListener" port="993"
secure="true"/>
</service>
<!-- POP3 service using mbox -->
<service class="org.bluezoo.gumdrop.pop3.DefaultPOP3Service">
<property name="mailbox-factory" ref="#mboxFactory"/>
<property name="realm" ref="#mailRealm"/>
<listener class="org.bluezoo.gumdrop.pop3.POP3Listener" port="110"/>
<listener class="org.bluezoo.gumdrop.pop3.POP3Listener" port="995"
secure="true"/>
</service>
SMTP services can also reference a mailbox factory for local delivery:
<service class="org.bluezoo.gumdrop.smtp.LocalDeliveryService">
<property name="mailbox-factory" ref="#mboxFactory"/>
<property name="local-domain">example.com</property>
<listener class="org.bluezoo.gumdrop.smtp.SMTPListener" port="25"/>
</service>
The LocalDeliveryService uses the mailbox factory to deliver
messages to local recipients.
The mbox format stores all messages in a single file, separated by "From " envelope lines. This format originated in Unix systems and remains widely used due to its simplicity.
Each message begins with a "From " line containing the sender and timestamp:
From sender@example.com Mon Jan 1 00:00:00 2025 [RFC 822 headers] [message body] From another@example.com Tue Jan 2 00:00:00 2025 [next message...]
Lines in the message body beginning with "From " are escaped by prepending ">" to prevent confusion with message boundaries.
<mailbox-factory id="mbox"
class="org.bluezoo.gumdrop.mailbox.mbox.MboxMailboxFactory">
<property name="base-directory">/var/mail</property>
<property name="extension">.mbox</property>
</mailbox-factory>
base-directory - directory containing user mailboxesextension - file extension for mbox files (default: .mbox)
User mailboxes are located at {base-directory}/{username}/INBOX{extension}.
Subfolders follow the same pattern with folder names as subdirectories.
Mbox is suitable for:
Maildir stores each message as a separate file within a directory structure. Developed by Daniel J. Bernstein for qmail, Maildir is designed for reliable delivery and concurrent access.
Each mailbox consists of three subdirectories:
new/ - newly delivered messages not yet seen by clientcur/ - messages that have been accessedtmp/ - temporary files during delivery (atomic rename)
Subfolders follow the Maildir++ convention, represented as directories
starting with a dot (e.g., .Sent, .Drafts).
Message metadata is encoded in the filename:
{timestamp}.{unique}.{hostname},S={size}:2,{flags}
Example: 1704067200.12345.mail.example.com,S=4096:2,S
Flag characters: S=Seen, R=Replied, F=Flagged, T=Trashed, D=Draft. This allows flag changes without modifying file contents—only a rename is required.
tmp/ then
renamed to new/, ensuring complete delivery.uidlist file tracks UIDs
for IMAP compatibility.keywordsnew/ to cur/
when first seen by a client
<mailbox-factory id="maildir"
class="org.bluezoo.gumdrop.mailbox.maildir.MaildirMailboxFactory">
<property name="base-directory">/var/mail</property>
</mailbox-factory>
base-directory - directory containing user Maildirs
User mailboxes are located at {base-directory}/{username}/ with the
standard cur/, new/, tmp/ subdirectories.
Maildir is recommended for:
Both mbox and Maildir implementations include a search indexing facility that dramatically accelerates IMAP SEARCH operations. Without indexing, each search requires parsing every message in the mailbox—a prohibitively slow operation for large mailboxes. The index pre-extracts searchable metadata, enabling most searches to complete without touching the underlying message files.
The search index (.gidx file) stores the following for each message:
All string values are stored in lowercase for case-insensitive searching.
Structured data is extracted using the RFC 5322 message parser—for example,
EmailAddress objects provide just the address without display
names or angle brackets.
To keep the index compact, the following are not indexed:
When a search requires body content (e.g., SEARCH BODY "invoice"),
the implementation falls back to the default parsing-based search for those
messages. However, the index can still accelerate compound searches by first
narrowing candidates using indexed criteria.
Beyond the primary entry list, the index maintains auxiliary data structures for O(1) and O(log n) lookups:
BitSet per flag enables instant
retrieval of all messages with a given flag (e.g., all unseen messages)
The index is stored as a binary file with the .gidx extension:
{mailbox}.mbox.gidx alongside the mbox file.gidx in the Maildir directoryThe file format includes:
HEADER (32 bytes):
magic: 4 bytes ("GIDX")
version: 2 bytes
flags: 2 bytes (reserved)
uidValidity: 8 bytes
uidNext: 8 bytes
entryCount: 4 bytes
headerChecksum: 4 bytes (CRC32)
ENTRIES SECTION:
For each message: [fixed header + property descriptors + variable data]
sectionChecksum: 4 bytes (CRC32)
Each entry uses "property descriptors" (offset + length pairs) for variable-length strings, allowing fast random access without parsing the entire entry. All strings are UTF-8 encoded.
The index is managed automatically:
The index includes multiple layers of corruption detection:
If corruption is detected, the index is discarded and rebuilt from the mailbox contents. No data is lost—the index is purely a cache of derived information.
| Search Type | Without Index | With Index |
|---|---|---|
| Flag search (e.g., UNSEEN) | O(n) message parses | O(1) BitSet lookup |
| Date range (SINCE/BEFORE) | O(n) message parses | O(log n) TreeMap |
| Size (LARGER/SMALLER) | O(n) stat calls | O(log n) TreeMap |
| Address (FROM/TO/CC) | O(n) header parses | O(n) scan, no parsing |
| Subject | O(n) header parses | O(n) scan, no parsing |
| Body text (BODY/TEXT) | O(n) full parses | O(n) full parses* |
* Body searches cannot be accelerated by indexing and always require
parsing. However, compound searches like UNSEEN BODY "invoice"
first filter by flag, reducing the messages that need parsing.
The index is designed with these principles:
Potential improvements noted for future development:
The mailbox API can be implemented for alternative storage backends. Potential implementations include:
When implementing a custom backend:
ReadableByteChannel for
message content to enable efficient transfer without loading entire messages
into memory.
public class DatabaseMailboxFactory implements MailboxFactory {
private final DataSource dataSource;
public DatabaseMailboxFactory(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public MailboxStore createStore() {
return new DatabaseMailboxStore(dataSource);
}
}
public class DatabaseMailboxStore implements MailboxStore {
private final DataSource dataSource;
private Connection connection;
private String username;
@Override
public void open(String username) throws IOException {
this.username = username;
try {
this.connection = dataSource.getConnection();
} catch (SQLException e) {
throw new IOException("Database connection failed", e);
}
}
@Override
public Mailbox openMailbox(String name, boolean readOnly) throws IOException {
return new DatabaseMailbox(connection, username, name, readOnly);
}
// ... implement remaining methods
}
← Back to Main Page | SMTP Server & Client | IMAP Server | POP3 Server | DNS Server | Telemetry
Gumdrop Mailbox API