# Mail

URL: https://zerodev.ca/docs/oasis/developer-documentation/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

| Model            | What it stores                                                                   |
| ---------------- | -------------------------------------------------------------------------------- |
| `mail_domains`   | Domain name, status, catch-all address and the DKIM key pair.                    |
| `mail_mailboxes` | Address, scrypt password hash, quota in MB and bytes used.                       |
| `mail_aliases`   | An address and the list of addresses it forwards to.                             |
| `mail_folders`   | Per-mailbox folders with IMAP `uid_next`, `uid_validity` and `modseq`.           |
| `mail_messages`  | One row per message: folder, UID, flags, size, summary fields and a storage key. |
| `mail_blobs`     | Message content when the database driver is used.                                |
| `mail_queue`     | Outgoing 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.

| Driver     | Key prefix | Where                                                  |
| ---------- | ---------- | ------------------------------------------------------ |
| `disk`     | `disk:`    | `data/mail/messages`, or the folder set in the config. |
| `s3`       | `s3:`      | Any S3 compatible bucket.                              |
| `database` | `db:`      | The `mail_blobs` table.                                |

## Handlers

| Handler     | Main methods                                                                                                                                                           |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `domains`   | `create`, `update` (pass `rotate` for a new DKIM key), `remove`, `records(id)` with a live DNS check.                                                                  |
| `mailboxes` | `create`, `update`, `remove`, `authenticate(address, password, ip)`, `resolve(address)`, `senders(mailbox)`.                                                           |
| `store`     | Folder methods (`list`, `folder`, `special`, `create`, `rename`, `drop`) and message methods (`deliver`, `read`, `rows`, `flag`, `copy`, `move`, `remove`, `expunge`). |
| `outbound`  | `send(sender, recipients, raw)` delivers locally and queues the rest. `process`, `retry`, `cancel`.                                                                    |
| `tls`       | `get()` 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.

```js
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.

| Section  | Keys                                                                                                   |
| -------- | ------------------------------------------------------------------------------------------------------ |
| Server   | `hostname` (empty uses `mail.<first domain>`), `max_size` (MB), `quota` (MB)                           |
| Storage  | `driver`, `disk_path`, `s3_endpoint`, `s3_region`, `s3_bucket`, `s3_key`, `s3_secret`, `s3_path_style` |
| TLS      | `tls_cert`, `tls_key` (file paths)                                                                     |
| Delivery | `relay_host`, `relay_port`, `relay_user`, `relay_password`, `retry_hours`                              |
