Oasis/developer documentation/Mail

Mail

Domains, mailboxes, folders, storage and the outgoing queue.

The mail part of core owns the data the SMTP, IMAP and webmail parts read and write. Other modules reach it through the core handlers, never its tables directly.

Models

ModelWhat it stores
mail_domainsDomain name, status, catch-all address and the DKIM key pair.
mail_mailboxesAddress, scrypt password hash, quota in MB and bytes used.
mail_aliasesAn address and the list of addresses it forwards to.
mail_foldersPer-mailbox folders with IMAP uid_next, uid_validity and modseq.
mail_messagesOne row per message: folder, UID, flags, size, summary fields and a storage key.
mail_blobsMessage content when the database driver is used.
mail_queueOutgoing mail per destination domain with retry state.

New mailboxes get INBOX, Sent, Drafts, Junk, Trash and Archive, each with its special-use flag.

Storage

The raw message is kept apart from its row. storage.put(buffer) returns a key with a driver prefix, so changing the driver later does not break older messages.

DriverKey prefixWhere
diskdisk:data/mail/messages, or the folder set in the config.
s3s3:Any S3 compatible bucket.
databasedb:The mail_blobs table.

Handlers

HandlerMain methods
domainscreate, update (pass rotate for a new DKIM key), remove, records(id) with a live DNS check.
mailboxescreate, update, remove, authenticate(address, password, ip), resolve(address), senders(mailbox).
storeFolder methods (list, folder, special, create, rename, drop) and message methods (deliver, read, rows, flag, copy, move, remove, expunge).
outboundsend(sender, recipients, raw) delivers locally and queues the rest. process, retry, cancel.
tlsget() returns the certificate and key from the config, or a self-signed pair for the hostname.

authenticate blocks an address and IP pair after 10 failures in 15 minutes. resolve follows aliases, catch-all addresses and name+tag@ addresses, and returns the local mailbox IDs and any outside addresses.

store.deliver(mailboxId, raw, options) checks the quota, allocates the next UID in a locked transaction and emits mail.changed with { mailbox, folder }. IMAP IDLE listens for that event.

const store = this.oasis.modules.handlers.get("core", "store");
await store.deliver(mailbox.id, raw, { special: "sent", flags: ["\\Seen"] });

Hostname

domains.hostname() is the name the servers use in their greeting, in Received headers, on the self-signed certificate and in the MX record. It is the hostname setting when one is set, otherwise mail. plus the first domain added, otherwise the machine name. Adding or removing a domain restarts the servers when the name changes.

records(id) lists the DNS records a domain needs. When Oasis runs in remote mode it also lists the A record that points the hostname at the server's public address.

Delivery

Outgoing mail is DKIM signed with the sender domain's key and grouped by destination domain. A clock runs outbound.process() every minute.

Mail goes through the relay host when one is set. Otherwise Oasis looks up the MX records and connects on port 25 with opportunistic TLS. A 5xx answer fails at once. Anything else retries after 5, 15, 30, 60, 120, 240 and 480 minutes until the retry window ends. Local senders get a bounce message when delivery fails.

Config

These keys are in the mail group of the core config.

SectionKeys
Serverhostname (empty uses mail.<first domain>), max_size (MB), quota (MB)
Storagedriver, disk_path, s3_endpoint, s3_region, s3_bucket, s3_key, s3_secret, s3_path_style
TLStls_cert, tls_key (file paths)
Deliveryrelay_host, relay_port, relay_user, relay_password, retry_hours