# The operator path Choosing an installation, getting started, configuring a mailbox at your provider, and administering the deployment. This is one reading path through the MailFathom user guide, every page of it in one file and in the order the guide walks them. `llms.txt` beside this file maps the whole of the documentation, and every link below is relative to the directory that map sits in. # Installing MailFathom MailFathom runs in four shapes, and each has one authoritative guide. This page is the decision: what each shape assumes, what it is good for, and what every shape shares. Follow the linked guide for the commands; the guides do not repeat each other and neither does this page. **A release publishes an image, the chart, the schema script, and the administrative command.** The image is `ghcr.io/krzysztof318/mailfathom:` and `docker.io/krzysztof318/mailfathom:` — one manifest list under one digest, so the registry to pull from is whichever your environment already reaches — with `latest` on the newest release's digest in both. The chart is `oci://ghcr.io/krzysztof318/charts/mailfathom` at the same version. Each release also attaches `mailfathom-schema-.sql` and its checksum, which is the schema step below, and one `mfctl` binary per platform with a checksum file covering all of them — [getting the command](operations/admin-endpoint.md#getting-the-command) is where that one is picked up, including the [install script](operations/admin-endpoint.md#on-linux-with-the-install-script) that does it in one line on Linux. Both packages are public, so pulling one needs no login. **`` is the release you are installing**, and [the releases page](https://github.com/Krzysztof318/MailFathom/releases) is where the current one is named. Pin it rather than tracking `latest`: an immutable tag is what makes a deployment reproducible and an upgrade a decision, which matters here because a new release can require a schema step before it will serve. **There is no binary artifact for the service itself**, so the native shape below is published from a checkout, and so are the Compose deployment and the Quadlet units, whose files live here and are versioned with the code that reads them. `mfctl` is the exception, and it is a client rather than the service: it runs on the machine you administer *from*. Beside the release runs the nightly channel: `ghcr.io/krzysztof318/mailfathom:nightly` — or `docker.io/krzysztof318/mailfathom:nightly`, which is the same digest in the other registry — and the `-nightly.-` tag of each night's build, published from `main` when it has moved. **A nightly is not a release and is a poor place to keep data you care about** — its schema can be ahead of any migration, it has no upgrade path in either direction, and it is deleted once thirty newer ones exist. [What a nightly build risks](operations/container-image.md#what-a-nightly-build-risks) states the whole of it before you choose one. ```bash git clone https://github.com/Krzysztof318/MailFathom.git cd MailFathom ``` **To try MailFathom before choosing any of this, `scripts/quick-start-compose.sh` prepares the Compose shape for you** — it asks where the mailbox lives and performs every step [deploying with Docker Compose](operations/deployment-compose.md#trying-it-first-with-one-command) otherwise asks you to type. What it produces is a deployment to evaluate with rather than one to depend on: it serves the machine it runs on over plain HTTP, keeps its credentials in files under the checkout, and backs nothing up. The choice below is the one it does not make for you, and it stays yours to make afterwards. ## Choosing a shape | Shape | Choose it when | Guide | | --- | --- | --- | | **Docker Compose** | You self-host on one machine and want the database, the network boundary, and the secret mounts arranged for you | [Deploying with Docker Compose](operations/deployment-compose.md) | | **Podman Quadlet** | You self-host on one machine, run Podman rootless, and want the container's secrets to be encrypted systemd credentials rather than plaintext files | [Deploying with Podman Quadlet](operations/deployment-quadlet.md) | | **Kubernetes with Helm** | You operate a cluster and bring your own Secret management | [Deploying to Kubernetes](operations/deployment-kubernetes.md) | | **Native process** | You run services under systemd without a container runtime, and want secrets delivered as systemd credentials | [Below](#native-process), then [secret provisioning](operations/secret-provisioning.md#native-systemd-service) | Docker Compose is the recommended first installation. It provisions PostgreSQL for you — `compose.yaml` creates the role, the database, and the `vector` extension on first start — and its defaults publish both ports on loopback, so nothing is reachable from another machine until you decide it should be. The Podman Quadlet is that same stack expressed as systemd units, and it provisions PostgreSQL the same way. What it buys is the one thing no Compose file can reach: a `.container` file is a systemd unit source, so the deployment's secrets are `LoadCredentialEncrypted=` credentials — ciphertext at rest, bound to the machine, decrypted only as the unit starts. What it asks in return is Podman rather than Docker, a rootless user, systemd 258 or later, and a decision about SELinux that its guide states before the first command. The Helm chart provisions one too, as a StatefulSet on a persistent claim, and turns it off for a deployment that has a server of its own. It creates no Secret, deliberately, so it needs an image reference and a Secret carrying the credentials; [what you supply](operations/deployment-kubernetes.md#what-you-supply) covers both, and the trade-off between the deployed database and one you operate, before the install command. The native process is the shape that brings no database at all. ## What every shape needs - **Linux.** It is the only platform this project officially supports, and everything below assumes it: the image is built for `linux/amd64` and `linux/arm64`, the native shape is a systemd service with systemd credentials, and TLS goes through the system OpenSSL. **MailFathom may well run on Windows — it is ordinary .NET — but expect problems and a setup of your own**: credential provisioning, TLS parameters, and file-permission expectations all differ there, nothing in this repository is verified against it, and a defect that reproduces only on Windows is not one this project can act on today. - **PostgreSQL with the `vector` extension.** The synchronized mail, its indexes, and the raw message content all live there. The Compose deployment, the Quadlet units, and the Helm chart bring their own (`pgvector/pgvector`, PostgreSQL 18); a native process expects yours, and the chart uses yours when you ask it to. - **An IMAP account to synchronize** and its password or app password, provisioned as a [secret reference](operations/secret-provisioning.md) rather than written into configuration. - **A data-encryption key, if any mailbox authenticates with OAuth.** MailFathom seals the refresh tokens it stores under one key the whole deployment shares, so generate it once before the first start and provision it like any other secret: ```bash openssl rand -base64 32 ``` It is `-base64 32`, not the `-base64 33` beside it for the database passwords: the value has to decode to exactly 32 bytes and a longer one is refused at startup. **Back it up with the database and never regenerate it** — the key is not in the database, and losing it means re-authorizing every mailbox. [Secret provisioning](operations/secret-provisioning.md#the-data-encryption-key) covers where it goes in each shape, and [`DataEncryption`](operations/configuration-runtime.md#dataencryption) the section that points at it. A deployment whose mailboxes all authenticate with a password needs none, and starts without one. - **OpenSSL 3.0 or later**, because MailFathom connects to the mail server over TLS and .NET hands every handshake to the system library. **1.1.1 is the floor below which nothing runs at all**: .NET 10 requires it on Unix and [fails to start](https://learn.microsoft.com/en-us/dotnet/core/compatibility/cryptography/10.0/openssl-version-requirement) without it. **Between the two, MailFathom may work and may not.** 1.1.1 has been out of upstream support since September 2023, nothing here is verified against it, and a defect that reproduces only there is not one this project can act on. Every current distribution ships 3.x, so this is a constraint on old machines rather than on new ones. The library's *security policy* is part of the installation too, not a detail of it: an OpenSSL that considers a server's cipher suite or key size too weak ends the connection before any credential is sent, and reports it as an authentication failure. **An installation that configures nothing runs at that full-strength policy and negotiates the newest TLS both ends support**, which is what almost every mail server wants. One that does not clear the policy is reached by opting in to a relaxed one — an OpenSSL configuration file named in the environment, which [the platform TLS policy](operations/platform-tls-policy.md) covers with a sample file. It is an exception you choose per deployment, never a default, and nothing in MailFathom's own configuration can substitute for it. - **An explicit schema step.** MailFathom never applies database migrations while starting: it verifies the schema and refuses to serve against one it does not recognize, so bringing a new build up *tells* you a migration is outstanding rather than silently applying one. What you apply is one idempotent SQL file — a release attaches `mailfathom-schema-.sql`, and a checkout produces the same file with `scripts/build-schema-artifact.sh`. Read it, back the database up, and run it with any PostgreSQL client; [applying the database schema](operations/database-schema.md) states the privileges it needs and what each startup failure means. ## Native process The publish output is self-contained in licensing terms — it carries `LICENSE` and `NOTICE` beside the binaries — but there is no packaged unit file or installer yet, so a native installation is assembled by hand: ```bash dotnet publish src/Host/Host.csproj --configuration Release --output /opt/mailfathom ``` Build with the SDK pinned in `global.json`. The process is then an ordinary ASP.NET Core service: - Configuration arrives through `appsettings.json` beside the binaries, a deployment-provisioned JSON file or directory named by [`ConfigurationSources`](operations/configuration-sources.md), command-line arguments, or environment variables. - Credentials arrive as systemd credentials: `LoadCredential=` in the unit, `systemd-credential:` references in the configuration. [Secret provisioning](operations/secret-provisioning.md#native-systemd-service) shows the unit fragment, including the encrypted-at-rest variant and the core-dump limit worth setting alongside it. The data-encryption key is one of them, provisioned no differently from a mailbox password: ```ini [Service] LoadCredentialEncrypted=mailfathom-data-key:/etc/mailfathom/mailfathom-data-key.cred ``` ```json { "DataEncryption": { "ActiveKeyId": "2026-08", "Keys": [ { "KeyId": "2026-08", "Material": { "Name": "mailfathom-data-key", "SecretReference": "systemd-credential:mailfathom-data-key" } } ] } } ``` `KeyId` is stored beside every value the key seals, so it is chosen once and never edited afterwards. Leave both out when no account authenticates with OAuth. **`systemd-creds encrypt` seals that `.cred` file against this machine** — by default against its TPM2 chip and its `/var/lib/systemd/` together — so the file opens here and on no other host, and it is not a backup of what it holds. Run the command on the machine that will read it, back the key's own base64 up with the database, and encrypt it again on a replacement machine rather than copying the sealed file across. [What an encrypted credential is bound to](operations/secret-provisioning.md#what-an-encrypted-credential-is-bound-to) states the whole binding, including which flag makes the chip a requirement rather than a preference and why a firmware update does not invalidate the credential. - [Where each surface is served](operations/configuration-endpoints.md#where-each-surface-is-served) is stated by each surface's own section. `McpEndpoint:BindAddress` and `McpEndpoint:Port` bind the protocol surface, `0.0.0.0:8080` by default, in clear text unless you configure otherwise. `ASPNETCORE_URLS`, `ASPNETCORE_HTTP_PORTS`, and `Kestrel:Endpoints` are refused at startup, so an address you state is never one the process quietly ignores. The health probes answer on their own port, `8081` by default; [health endpoints](operations/health-endpoints.md) records how to move or disable that listener. - PostgreSQL, the `vector` extension, and the schema step are yours, exactly as they are under Kubernetes. - A mail server whose TLS parameters the machine's own OpenSSL refuses is reached by naming an OpenSSL configuration file in the service's environment, which is a pre-start concern no MailFathom setting can replace. [The platform TLS policy](operations/platform-tls-policy.md) has the sample file and the unit fragment. ## Verifying any installation An installation is done when the probes answer and the log shows a healthy start: ```bash curl -fsS http://127.0.0.1:8081/started # startup gates have passed curl -fsS http://127.0.0.1:8081/health # ready, including the database curl -fsS http://127.0.0.1:8081/alive # the process itself ``` A refusal at startup is designed to name the setting that caused it — a missing secret reference, a pending migration, an unsafe transport combination — so the log is the first place to look, and the message quotes the configuration key to fix. Continue with [getting started](users/getting-started.md): provisioning the secrets, configuring the first account, and connecting an MCP client. # Getting started This page walks from an installed MailFathom to a first successful tool call: provision the credentials, configure a mailbox, start the service, verify it, connect an MCP client, and read a result correctly. It assumes an installation from [installing MailFathom](users/installation.md); a developer evaluating from the checkout can run the [Aspire orchestration](operations/local-development.md#running-locally-with-aspire) instead, which provisions PostgreSQL and applies the schema on its own. **Somebody evaluating MailFathom on the Compose shape can have steps 1 to 6 performed for them.** `scripts/quick-start-compose.sh` asks the same questions this page does, writes the same values, and ends by printing the two a client needs in step 7 — [trying it first, with one command](operations/deployment-compose.md#trying-it-first-with-one-command) is that path, including what a deployment it prepares is missing before anybody depends on it. Read this page anyway, for what each answer means. The examples write configuration as JSON. Every setting can arrive as an environment variable instead — `:` becomes `__`, so `MailSynchronization:Enabled` is `MailSynchronization__Enabled` — and the [configuration reference](operations/configuration-reference.md) maps every key used below to the page that states it. ## 1. Provision the secrets MailFathom's configuration never carries a credential. A secret-bearing setting holds a *reference* — `file:/…`, `systemd-credential:…`, `env:…` — and the material lives wherever the deployment provisions it. A configuration file is therefore safe to review and back up: leaking it leaks paths, not passwords. You need two pieces of material before anything is configured: - **The mailbox password** or app password of the IMAP account to synchronize. A mailbox whose provider no longer accepts one — a Google Workspace or Exchange Online account — is authenticated with an OAuth refresh token instead; obtain it first with [mailbox OAuth](operations/mailbox-oauth.md) and substitute that block for the password below. That account needs a third piece of material as well, the **data-encryption key** the rotated refresh token is sealed under: `openssl rand -base64 32`, generated once and never regenerated, provisioned as a reference like everything else here. [The data-encryption key](operations/secret-provisioning.md#the-data-encryption-key) is the whole of it. - **An MCP API key** for the client that will connect. Generate it rather than inventing it: ```bash openssl rand -base64 33 | tr -d '\n' > mcp-workstation-key ``` Where the files go is the deployment's convention: `secrets/mailfathom/` for [Compose](operations/deployment-compose.md#credentials), a Kubernetes `Secret` mounted at `/etc/mailfathom/secrets` for [the chart](operations/deployment-kubernetes.md#what-you-supply), `LoadCredential=` for [systemd](operations/secret-provisioning.md#native-systemd-service), and an encrypted credential under `~/.config/credstore.encrypted/` for [the Quadlet](operations/deployment-quadlet.md#the-credentials). The references below assume the mounted directory the Compose and Helm shapes share. ## 2. Configure the mailbox Synchronization is off until configuration turns it on, and an enabled synchronization requires at least one account: ```json { "MailSynchronization": { "Enabled": true, "Accounts": [ { "AccountId": "primary", "DisplayName": "Personal mail", "Host": "imap.example.test", "Port": 993, "UserName": "you@example.test", "Secrets": { "Password": { "Name": "imap-primary-password", "SecretReference": "file:/etc/mailfathom/secrets/imap-primary-password" } }, "Folders": [ { "Alias": "inbox", "SpecialUse": "Inbox" }, { "Alias": "sent", "SpecialUse": "Sent" } ] } ] } } ``` **What goes in `Host`, `Port`, and `Secrets` depends on where the mailbox lives**, and so does whether IMAP has to be switched on first and whether a password is accepted at all. [Configuring a mailbox at your provider](users/mailbox-providers.md) has the address and the credential kind for the popular mail services, and what each one does differently once synchronization is running. Points worth knowing before you adapt it: - **`AccountId` and `Alias` are your names**, not the server's. They are what every tool argument, log line, and error message uses, so pick names you are happy to see in a diagnostic. - **`DisplayName` is the name an assistant reads back to you.** It is required and has no default: the identifier above is a key, and a person hearing "the email came from `acct-2`" learns nothing. It travels beside the identifier in every tool result, and either spelling narrows a listing, a search, or a question to that mailbox. No two accounts may share one, and none may take another account's identifier, so a name always names one mailbox. - **Folders are best named by role.** `SpecialUse` lets discovery find the folder whatever the server calls it — a German server's `Gesendet` is still `Sent` — and configuring no folder at all synchronizes the inbox. Naming an exact server path is the alternative for folders with no role, and you may name both: the path finds the folder and the role says what it is for, so a rule or a tool can go on asking for `role:Junk` on a server that advertises nothing. One role belongs to one folder per account. [What a role says, beside how a folder is found](features/imap-synchronization.md#what-a-role-says-beside-how-a-folder-is-found) states what naming a role buys you everywhere else. - **A folder left out of the list is a folder MailFathom does not have.** The list is not a filter over your mailbox; it is the whole of what this deployment knows about. A folder no entry names is never mirrored, never listed, searched, read, or answered from, never embedded, and never evaluated by a rule — and so is mail an earlier configuration had already stored under an entry you later removed, which stays in the database and stops being reachable. *Working out the list* below is how to arrive at one you meant. - **A mapped folder is mirrored, embedded, and readable by tools** unless you say otherwise. `Synchronize`, `GenerateEmbeddings`, and `VisibleToTools` each default to `true` on a folder entry, and switching one off is how a folder stays nameable while its mail stays out of the local copy, out of an embedding provider, or out of everything an assistant can read. Mail the folder had already stored is kept when you turn mirroring off, withheld from everything that reads a mailbox, so turning it back on later fetches what arrived meanwhile rather than the whole folder again. [What a mapping decides beyond where the folder is](features/imap-synchronization.md#what-a-mapping-decides-beyond-where-the-folder-is) states what each one costs, including what happens to mail already stored when you turn mirroring off. - **A folder you name is created only if you ask for it.** Add `CreateIfMissing: true` beside a `RemotePath` and MailFathom creates that folder on its first run, when your server has none at it — which is what a rule filing mail into an archive folder you decided on needs, without a detour through a mail client. A mapping that also carries `Synchronize: false` gets its folder the first time something files mail into it. The switch defaults to `false`, so leaving it out keeps a mistyped path reporting itself as an alias that resolves to nothing rather than becoming a folder named after the typo. Nothing else about your folders is ever changed: MailFathom never renames, deletes, or unsubscribes from one. [A folder the mapping asked for is created](features/imap-synchronization.md#a-folder-the-mapping-asked-for-is-created) states when it happens and what a server's refusal reports. - **The transport is TLS by default.** Port 993 with TLS-on-connect is the default posture, and every weakening — an unencrypted connection, clear-text authentication over one — must be stated explicitly and fails startup otherwise. A server with a private certificate authority is supported by trusting that authority, never by turning validation off; [transport security](features/imap-synchronization.md#transport-security) records the rules. - **An older server may be refused before any of this applies.** If synchronization reports an authentication failure wrapping `SSL Handshake failed with OpenSSL error`, the password is not the problem: the platform's own TLS policy ended the handshake before a credential was sent. [The platform TLS policy](operations/platform-tls-policy.md) covers how to confirm that and the one supported way to relax it. - **How far back to synchronize** is per account: `EarliestEmailReceivedDate` bounds the first synchronization of a large mailbox, and omitting it copies everything the server still holds. - **New mail arrives on the next run unless you ask for push.** The default reconciles each account every five minutes. Setting an account's `"Mode": "Push"` makes MailFathom hold an IMAP connection open and synchronize the moment the server reports a change. What that costs depends on the server: one connection for the whole account where it supports the `NOTIFY` extension, one per watched folder where it supports only `IDLE`, and nothing at all where it supports neither — which is polled instead and says so in the log. Push **adds** to the schedule rather than replacing it — the account still runs on its `Interval`, and a notification only starts the next run sooner. [Push synchronization](features/imap-synchronization.md#push-synchronization) records the whole model. ### Working out the list Nothing discovers folders into that list for you, so writing it is a step of its own. From an empty configuration: 1. **Look at what the server actually holds.** Any mail client you already use lists the account's folders, and the paths it shows are the paths `RemotePath` takes — including their separator, which is `/` on some servers and `.` on others. [Configuring a mailbox at your provider](users/mailbox-providers.md) names the folders the popular services create and which of them carry a role, so a Gmail account is a different starting list from a Fastmail one. 2. **Name by role whatever carries one.** `Inbox`, `Sent`, `Drafts`, `Junk`, `Trash`, `Archive`, `All`, `Flagged`, and `Important` are the roles a server can advertise; a mapping that names one finds the folder whatever it is called and keeps working when the localization or the path changes. Each role belongs to one folder per account. 3. **Name the rest by path.** A project folder, a mailing-list folder, a folder a filter on the server files into — none of these carries a role, so each takes a `RemotePath` and an alias of your own. 4. **Create the folder that is not there yet.** A folder you want a rule to file into — an archive of your own, say — does not have to be made in a mail client first: give the mapping its `RemotePath` and `CreateIfMissing: true`, and MailFathom creates it on the first run that resolves the alias. It is the only thing MailFathom ever changes about your mailbox's shape. 5. **Leave out what you do not want mirrored.** Leaving a folder out is the way to keep its mail out of MailFathom entirely, and it stays out until an entry names it. The account's `Folders` then grows from the two entries above into the list you meant, mixing the three kinds of entry: ```json { "Folders": [ { "Alias": "inbox", "SpecialUse": "Inbox" }, { "Alias": "sent", "SpecialUse": "Sent" }, { "Alias": "spam", "RemotePath": "INBOX.Spam", "SpecialUse": "Junk" }, { "Alias": "projects", "RemotePath": "INBOX.Work.Projects" }, { "Alias": "archive", "RemotePath": "INBOX.Archive", "SpecialUse": "Archive", "CreateIfMissing": true } ] } ``` Once the service is running, `list_accounts` reports one entry per folder this deployment mapped and lets tools read, with when each last synchronized — so a mistyped path shows up as a folder missing from that answer rather than as one holding no mail. The log names an alias that resolved to nothing or to more than one folder, in both cases naming the remedy. The Compose deployment reads this from `config/10-mailfathom.json`; Kubernetes mounts it as a ConfigMap key; a native process names the file through [`ConfigurationSources`](operations/configuration-sources.md). ## 3. Point it at the database The Compose deployment wires the connection and its password for you — skip this step there. Elsewhere, the connection string carries everything but the password, and the password joins it through a reference: ```json { "ConnectionStrings": { "mailfathom": "Host=db.example.test;Database=mailfathom;Username=mailfathom" }, "Persistence": { "Password": { "Name": "mailfathom-database-password", "SecretReference": "file:/etc/mailfathom/secrets/mailfathom-database-password" } } } ``` ## 4. Start it, and apply the schema Start the service the way the installation shape does. The first start against an empty database **fails on purpose**, naming the migration it expects: ```text The database has not applied 1 migration(s) this build defines: 20260731132336_Initial. ``` That is the explicit schema step described in [installing MailFathom](users/installation.md#what-every-shape-needs): MailFathom verifies the schema and refuses to serve against one it does not recognize, and applying migrations is a step you take, with a backup first once there is data to lose. What you apply is one SQL file. A release attaches it; from a checkout, generate it: ```bash scripts/build-schema-artifact.sh # artifacts/schema/mailfathom-schema-.sql ``` Read it, apply it with any PostgreSQL client, and start again — the refusal is gone. The role that applies it needs more privilege than the one MailFathom connects as, which [applying the database schema](operations/database-schema.md) explains along with the grants that leaves behind. ## 5. Verify it is healthy ```bash curl -fsS http://127.0.0.1:8081/started curl -fsS http://127.0.0.1:8081/health ``` `/started` confirms the startup gates passed — every secret reference resolved, the schema matched. `/health` is readiness and includes the database. Both answer on the probe listener, `8081` by default, never on the application port; [health endpoints](operations/health-endpoints.md) explains that separation. Then let the first synchronization run. Its progress is visible in the log — each folder run reports what it stored — and, once you can call a tool, in the `folderFreshness` every result carries. A large mailbox takes a while on the first pass; later runs move only what changed, every five minutes by default. ## 6. Enable the MCP endpoint The endpoint is off by default, and enabling it means stating how it is authenticated: ```json { "McpEndpoint": { "Enabled": true, "Authentication": [ { "ApiKey": { "Name": "workstation", "SecretReference": "file:/etc/mailfathom/secrets/mcp-workstation-key" } } ] } } ``` An empty `Authentication` list is legal — the reverse-proxy-and-loopback deployment is an ordinary one — but it is announced with a startup warning, because an unauthenticated endpoint serves your mailbox to whoever can reach its port. Read [the MCP endpoint](operations/mcp-endpoint.md) before widening anything: it records the OAuth alternative, browser origins, serving your own domain over TLS, client certificates, and the rate limits that apply out of the box. **A credential reaches the whole surface until its entry narrows it.** The entry above writes no `Permissions` list, so the key it configures may do everything the MCP surface publishes — read the local mailbox copy, ask questions of it, and read, record, amend, and erase the deployment's own contact book. That is deliberate: nothing has to be granted before a first deployment works. Writing a `Permissions` list on the entry states a narrower grant, and startup reports what every entry resolved to either way — [what a credential may do](operations/mcp-endpoint.md#what-a-credential-may-do) has the names and the rules. A narrowed entry is enforced on the tools themselves: a client connecting with that credential is listed only the tools its grant permits, and a call naming any other is answered as a call naming a tool that does not exist. So a grant is what a credential may reach rather than a note about it. **What the endpoint offers at all is a separate decision**, and it needs no attention on a first deployment: every kind of tool is published unless `McpEndpoint:PublishedToolCategories` names some. An instance stood up for retrieval can write `["mailbox"]` there and publish the reading tools alone, so a release adding tools does not change what your endpoint offers without you noticing. [What this endpoint publishes](operations/mcp-endpoint.md#what-this-endpoint-publishes) holds the categories and what a client may narrow for its own session. MailFathom itself serves plain HTTP unless its own TLS termination is configured, so keep the application port on loopback or behind a TLS-terminating proxy — the Compose deployment's default — and give the proxy the certificate. If you put a proxy in front, name it in `ReverseProxy:TrustedProxies` as well. The public scheme and host survive the hop either way; naming the proxy is what stops anything else that can reach the port from claiming them: [behind a TLS-terminating reverse proxy](operations/mcp-endpoint.md#behind-a-tls-terminating-reverse-proxy). ## 7. Connect an MCP client The endpoint speaks the MCP **Streamable HTTP** transport at `/mcp` — the path is fixed — and the key travels as a bearer credential. Any client that supports Streamable HTTP connects with two facts: | The client asks for | The value | | --- | --- | | Server URL | `http://127.0.0.1:8080/mcp` under the Compose defaults; your proxy's HTTPS address otherwise | | Header | `Authorization: Bearer ` | **Where a client asks for those two values, and whether it will accept the second at all, differs per client.** Two of the popular chat clients offer no field for a static header, which decides the deployment's authentication rather than only the setup steps; [connecting the chat client you already use](users/mcp-clients.md) has the steps, the address kind, and the authentication shapes for each one by name. A connected client's tool listing should show at least fourteen tools — `list_accounts`, `list_emails`, `get_email_content`, `search_emails`, each advertising itself as read-only, non-destructive, and idempotent; `set_mail_flags`, which marks, stars, and labels a message and announces itself as neither read-only nor confined to this process, and as destructive because a keyword replacement states the whole set; and `send_email`, `reply_to_email`, and `forward_email`, which announce themselves the same way and are destructive on the other ground — each takes nothing away and nothing takes it back. Beside those, `get_outgoing_email` reports what became of a message one of them queued and advertises itself as read-only, and `cancel_outgoing_email` stops one that has not left yet, advertising itself as destructive and — unlike the sending tools — as confined to this process, since stopping a message reaches nobody. Then `save_draft`, `update_draft`, and `delete_draft`, which write a message into your own Drafts folder and send nothing, and `send_draft`, which sends what one of them holds and announces itself exactly as `send_email` does. Then the six contact tools over the deployment's own contact book, of which four change state and two — `update_contact`, which replaces a whole record, and `delete_contact`, which erases one — announce themselves as destructive too. `ask_mail` appears only once you have configured a chat model and an embedding model and both are working; until then its absence is the deployment telling you it cannot answer questions yet rather than a fault. [Verifying an enabled endpoint](operations/mcp-endpoint.md#verifying-an-enabled-endpoint) is the checklist form of this, including what the refusals look like when the key or the origin is wrong. **Signing a person in through your own identity provider instead of sharing a key** is the other way to connect, and it is the only way for a client whose dialog takes no header. Most of that work is in the provider rather than here: [MCP client OAuth](operations/mcp-client-oauth.md) walks it end to end from a deployment in exactly this state. ## 8. Make the first call, and read it correctly Ask the connected agent to list recent mail, or have the client call `list_emails` with a small page. Two parts of the result matter more than the emails on the first day: - **`folderFreshness`** carries one entry per folder in scope, stating when synchronization last committed progress there. An empty page whose entries report `wasSynchronized: false` means the folder has not synchronized yet — not that the mailbox is empty. This is the field to check before trusting any early result. - **`nextCursor`** is how the rest of the timeline is read: pass it back unchanged with the same filters. Results are deliberately bounded — at most 100 summaries per page — so an agent reads pages, not mailboxes. From here, [using the tools](users/usage.md) describes the day-to-day surface: what each tool answers, what it bounds, and what its errors mean, and [administering your deployment](users/administering.md) covers reaching the running service from your own machine with the `mfctl` command. # Configuring a mailbox at your provider > [!WARNING] > Some of the steps on this page are performed in a product this project does not control. Any screen, menu, or field > named here can be renamed or moved there at any time. Where this page and that product's own documentation disagree, > the product's documentation is right. [Getting started § configure the mailbox](users/getting-started.md#2-configure-the-mailbox) shows the account block and what each of its keys is for. This page answers the question that comes next and that no reference page can answer, because the answer belongs to somebody else: **what goes in `Host`, `Port`, `Secrets`, and `TransportSecurity` for the mail service I actually use, and what does that service do differently once synchronization is running.** Every mail service named below is reached the same way — MailFathom speaks IMAP over TLS and nothing else, and there is no per-provider code path anywhere in it. Four things differ, and a reader has to get each right before the first connection succeeds: the address, whether IMAP has to be switched on first, which credential the service will accept, and what the service's own behaviour does to a mailbox once mail starts arriving in the local copy. ## Two claims this page does not make **Presence is a check at a point in time, not a supported-provider list.** Every entry below is what that service's own current documentation said on the date the entry carries. None of these services is under this project's control, and any of them may change the answer next week without anybody here touching anything. An entry that has stopped being true is a defect in this page rather than in the deployment that trusted it. **Absence is not a refusal.** A service missing from this page is not blocked, unsupported, or known to fail — it is unchecked. MailFathom reaches any IMAP server that will serve a TLS connection and an authentication mechanism the account permits, and [an IMAP server you run yourself](#an-imap-server-you-run-yourself) is the section for one. ## What the evidence column means Each entry says which of two kinds of evidence it rests on, because they are not the same claim and blurring them is how a guide starts lying: - **Documented** — the service's own current documentation was read on the date the entry carries. It establishes what the service publishes about its address, its credential, and its own behaviour. It does not establish that a connection was made. - **Observed** — a MailFathom deployment ran against a mailbox at that service and the behaviour was what the entry says. **Every entry on this page today is `Documented`.** That is deliberate rather than a gap waiting to be filled: a third party's mail server is not something this repository verifies, and an entry claiming otherwise would be asserting a test that nothing here runs. Where a reader needs a behaviour confirmed against their own mailbox, [what your own server actually offers](#what-your-own-server-actually-offers) is how the running deployment answers it. ## The account block, written once This is the complete shape, and it is shown once. Every section below changes three parts of it at most — the address, the credential block, and, in one case, `TransportSecurity`: ```json { "MailSynchronization": { "Enabled": true, "Accounts": [ { "AccountId": "primary", "DisplayName": "Personal mail", "Host": "imap.example.test", "Port": 993, "UserName": "you@example.test", "Secrets": { "Password": { "Name": "imap-primary-password", "SecretReference": "file:/etc/mailfathom/secrets/imap-primary-password" } }, "Folders": [ { "Alias": "inbox", "SpecialUse": "Inbox" }, { "Alias": "sent", "SpecialUse": "Sent" } ] } ] } } ``` A service that will not accept a password takes an `OAuth` block in place of `Secrets` instead; [mailbox OAuth § configuring the account](operations/mailbox-oauth.md#configuring-the-account) holds that form in full, and no section below repeats it. [Mail configuration § `MailSynchronization`](operations/configuration-mail.md#mailsynchronization) is the inventory of every key named on this page, with its type, default, and constraint. ## What is the same everywhere Four things hold whichever service a mailbox lives at, and each is stated in full on the page that owns it rather than per provider here: - **The transport is TLS on connect, on port 993, by default.** Every weakening — an unencrypted connection, clear-text authentication over one — is an explicit opt-in that fails startup otherwise, and certificate validation cannot be turned off at all. A server with a private certificate authority is reached by trusting that authority. [IMAP synchronization § transport security](features/imap-synchronization.md#transport-security) records the rules. - **Folders are named by role rather than by path.** `SpecialUse` lets discovery find a folder whatever the server calls it, which matters more here than it looks: the same role carries a different path at almost every service below. A service that advertises no role for a folder loses none of this — name the path and the role together, and everything that asks for `role:Junk` still reaches it. [Folder aliases and discovery](features/imap-synchronization.md#folder-aliases-and-discovery) covers the matching, [what a role says](features/imap-synchronization.md#what-a-role-says-beside-how-a-folder-is-found) the rest, and [the folders, and what each service calls them](#the-folders-and-what-each-service-calls-them) what you are naming. - **Reading never marks mail read.** MailFathom does not set the remote `\Seen` flag while synchronizing, reconciling, fetching content, or answering a tool call — the sessions those run on hold no operation capable of writing a flag. If mail is turning up read at your service, it is another client or a rule at the service, not this one. [Marking mail read is an act, never a side effect of reading](features/imap-synchronization.md#marking-mail-read-is-an-act-never-a-side-effect-of-reading) states the guarantee and its one deliberate exception. - **Whether `"Mode": "Push"` does anything is the server's answer, not a setting.** Push needs the `IDLE` extension, and one connection for the whole account instead of one per folder additionally needs `NOTIFY`. A server offering neither is polled on the account's interval and says so in the log. [Push synchronization](features/imap-synchronization.md#push-synchronization) holds the whole model. ## The addresses, in one table | Service | `Host` | `Port` | Switch IMAP on first | Credential the service will accept | Evidence | | --- | --- | --- | --- | --- | --- | | [Gmail, on a personal account](#gmail-on-a-personal-account) | `imap.gmail.com` | `993` | No — always on | App password, or OAuth | Documented, 2026-08-12 | | [Gmail, on a Google Workspace account](#gmail-on-a-google-workspace-account) | `imap.gmail.com` | `993` | Yes, by an administrator | **OAuth only** | Documented, 2026-08-12 | | [Outlook.com](#outlookcom) | `outlook.office365.com` | `993` | Yes, in the mailbox settings | **OAuth only** | Documented, 2026-08-12 | | [Exchange Online and Microsoft 365](#exchange-online-and-microsoft-365) | `outlook.office365.com` | `993` | No — on unless an administrator turned it off | **OAuth only** | Documented, 2026-08-12 | | [Yahoo Mail](#yahoo-mail) | `imap.mail.yahoo.com` | `993` | Not documented as required | App password | Documented, 2026-08-12 | | [iCloud Mail](#icloud-mail) | `imap.mail.me.com` | `993` | Not documented as required | App-specific password | Documented, 2026-08-12 | | [Proton Mail](#proton-mail-through-the-local-bridge) | `127.0.0.1` (the local bridge) | `1143` | The bridge is installed and signed in instead | The bridge's own generated password | Documented, 2026-08-12 | | [Fastmail](#fastmail) | `imap.fastmail.com` | `993` | Not documented as required | App password | Documented, 2026-08-12 | | [Zoho Mail](#zoho-mail) | `imap.zoho.com` or `imappro.zoho.com` | `993` | Yes, in webmail | Account password, or an application-specific one | Documented, 2026-08-12 | Proton Mail is the one row whose address is not the mail service and whose port is not 993, for the reason its section gives. Everything else takes the account block above with the address and the credential replaced. *Not documented as required* is the honest answer rather than a *no*: the service's own setup documentation names no switch, and this review made no connection that would establish one. ## The folders, and what each service calls them **MailFathom mirrors the folders your configuration names and nothing else.** Nothing discovers a folder into that list, so a folder left out of it is a folder this deployment does not have — not one it holds and hides. [Getting started § working out the list](users/getting-started.md#working-out-the-list) is how to arrive at one; this table is the part of it that depends on where the mailbox lives. | Service | What its own folders are called | Roles the server advertises | Evidence | | --- | --- | --- | --- | | Gmail, personal and Workspace alike | `INBOX`, and the system labels under a `[Gmail]` prefix: `[Gmail]/All Mail`, `[Gmail]/Drafts`, `[Gmail]/Important`, `[Gmail]/Sent Mail`, `[Gmail]/Spam`, `[Gmail]/Starred`, `[Gmail]/Trash` | Documented: `\All`, `\Drafts`, `\Important`, `\Sent`, `\Junk`, `\Flagged`, `\Trash` | Documented, 2026-08-12 | | Outlook.com, Exchange Online, Microsoft 365 | Inbox, Drafts, Sent Items, Deleted Items, Junk Email, Archive | Not documented in the pages this review read | Documented, 2026-08-12 | | Yahoo Mail | Inbox, Draft, Sent, Archive, Spam, Trash | Not documented in the pages this review read | Documented, 2026-08-12 | | iCloud Mail | Inbox, VIP, Drafts, Sent, Archive, Trash (Bin in some regions), Junk | Not documented in the pages this review read | Documented, 2026-08-12 | | Proton Mail, through the bridge | All Mail, Inbox, Drafts, Sent, Starred, Archive, Spam, Trash, and each of your labels as a folder under `Labels` | Not documented in the pages this review read | Documented, 2026-08-12 | | Fastmail | Inbox, Archive, Drafts, Sent, Spam, Trash | Not documented in the pages this review read | Documented, 2026-08-12 | | Zoho Mail | Inbox, Drafts, Sent, Spam, Trash | Not documented in the pages this review read | Documented, 2026-08-12 | **A name in that middle column is what the service calls the folder, not necessarily the IMAP path.** A service may serve its folders under a prefix, translate them into the account's language, or rewrite a character its own interface allows — Gmail's prefix and Fastmail's replacement of `.` in a custom folder name are both documented behaviours. That is the argument for naming a folder by its role wherever it has one: `SpecialUse` finds the folder whatever path it is at, and the configuration survives a rename, a language change, and a move to another service. **"Not documented" is not "not advertised".** Only Google publishes which RFC 6154 attributes its server sends, so the other rows say what this review could establish rather than what the server does. Where a role matters and the service documents no attribute, name the folder's path and its role together — `{ "Alias": "spam", "RemotePath": "Junk Email", "SpecialUse": "Junk" }` — and everything that asks for `role:Junk` reaches it regardless of what the server advertised. A mapping whose role finds nothing reports the alias as unresolved in the log rather than failing the account. Sources: [Gmail IMAP extensions](https://developers.google.com/workspace/gmail/imap/imap-extensions), [Working with message folders in Outlook.com](https://support.microsoft.com/en-US/Outlook/working-with-message-folders-in-outlook-com), [Archive and un-archive messages in Yahoo Mail](https://help.yahoo.com/kb/SLN26466.html), [Organize email with folders in Mail on iCloud.com](https://support.apple.com/guide/icloud/mm6b1a6730/icloud), [Labels in Bridge](https://proton.me/support/labels-in-bridge), [Setting up and using folders](https://www.fastmail.help/hc/en-us/articles/1500000280301-Setting-up-and-using-folders), [Using folders](https://www.zoho.com/mail/help/using-folders.html). ## Gmail, on a personal account Google documents `imap.gmail.com` on port `993` with SSL required, and states that IMAP access is always on and no longer has a setting to switch — so nothing has to be enabled first on a personal account. **The credential.** An app password works with the ordinary `Secrets:Password` block and is the shorter path; OAuth is the alternative and is the same setup a Google Workspace mailbox needs. [Mailbox OAuth § do you need this at all](operations/mailbox-oauth.md#do-you-need-this-at-all) is where the choice is already stated in full — which accounts can be issued an app password at all, and the review obligation that comes with registering your own Google project for mail access — and it is not repeated here. **Labels arrive as folders, and one message can be in several of them.** Google publishes labels over IMAP as folders, with its own labels under a `[Gmail]` prefix, and exposes each message's label set through the `X-GM-LABELS` attribute. `[Gmail]/All Mail` holds everything, so a message carrying two labels is reachable through both label folders and through All Mail as well. Configure the folders you want by role — an inbox, a sent folder — rather than synchronizing All Mail beside them, or the same message is copied under several aliases and counted several times in every listing. **Two limits worth knowing before the first synchronization of a large mailbox.** Google documents a daily IMAP ceiling of 2500 MB downloaded and 500 MB uploaded — synchronization only reads, so the download figure is the one a first pass over a large mailbox can reach — and describes a safeguard that suspends the account for about an hour, and for as long as a day, once a limit is hit. It separately documents that an account may be added to at most 15 mail clients at once, which a deployment watching many folders in push mode can approach on its own. `EarliestEmailReceivedDate` is what bounds the first pass against the first limit, and `MaxSubscribedFolders` bounds the connection count against the second. Sources: [Add Gmail to another email client](https://support.google.com/mail/answer/7126229), [IMAP, POP, and SMTP](https://developers.google.com/workspace/gmail/imap/imap-smtp), [IMAP extensions](https://developers.google.com/workspace/gmail/imap/imap-extensions), [Gmail bandwidth limits](https://support.google.com/a/answer/1071518). ## Gmail, on a Google Workspace account The address is the same as above. Two things are not. **An administrator turns IMAP on first.** Google documents the setting in the admin console under **Apps → Google Workspace → Gmail → End User Access → POP and IMAP access**, applied to the whole organization or to one organizational unit. The same setting optionally restricts access to named OAuth client identifiers, which is worth checking if the first connection is refused after the switch is on. **A password will not authenticate.** Google states that as of 1 May 2025 a Workspace account no longer accepts a sign-in from an application using a username and password, and an app password cannot be issued for one at all. The account therefore carries an `OAuth` block and no `Secrets:Password`, with `PermittedAuthenticationMechanisms` set to the token-bearing mechanisms — which is what frees the account from configuring a password. [Mailbox OAuth § Google](operations/mailbox-oauth.md#google) is the registration, and [§ configuring the account](operations/mailbox-oauth.md#configuring-the-account) is the block. The label and All Mail behaviour and both limits are Gmail's and apply here unchanged. Sources: [Turn POP & IMAP on or off for users](https://support.google.com/a/answer/105694), [Add Gmail to another email client](https://support.google.com/mail/answer/7126229). ## Outlook.com Microsoft documents `outlook.office365.com` on port `993` with SSL/TLS, and lists the authentication method for it as OAuth2 and modern authentication rather than a password. **IMAP is switched on in the mailbox.** Microsoft's own settings page states that POP and IMAP access is off by default and is enabled under **Settings → Mail → Forwarding and IMAP**. **A password will not authenticate, and neither will an app password.** Microsoft states that basic authentication stopped working for these accounts on 16 September 2024 and that devices using POP or IMAP can no longer use app passwords. So an Outlook.com mailbox takes the `OAuth` block, registered through Microsoft Entra exactly as a Microsoft 365 mailbox is; [mailbox OAuth § Microsoft — Entra](operations/mailbox-oauth.md#microsoft--entra) is the registration. The delegated permission is the one a personal mailbox owner can consent to themselves. Sources: [POP, IMAP, and SMTP settings for Outlook.com](https://support.microsoft.com/en-us/office/pop-imap-and-smtp-settings-for-outlook-com-d088b986-291d-42b8-9564-9c414e2aa040), [Modern authentication methods now needed to continue syncing Outlook email in non-Microsoft email apps](https://support.microsoft.com/en-us/support/known-issues/modern-authentication-methods-now-needed-to-continue-syncing-outlook-email-in-non-microsoft-email-ap). ## Exchange Online and Microsoft 365 The address and port are the same as Outlook.com's, and so is the answer about passwords — but for a different reason and with a different consequence, which is why this is a section of its own. **Basic authentication was removed rather than discouraged.** Microsoft states that it removed the ability to use basic authentication for IMAP in Exchange Online, that no one — customer or Microsoft support — can re-enable it, and that the same change prevents the use of app passwords. OAuth 2.0 is the only way in, and Microsoft's own guidance to application developers is to keep the protocol and implement it. **Both grants are available here, and they are different mailboxes' answers.** A delegated registration carrying `IMAP.AccessAsUser.All` acts for one mailbox owner and needs a refresh token obtained once; an app-only registration carrying `IMAP.AccessAsApp` acts for the application, needs tenant administrator consent, and uses the `client_credentials` grant with no sign-in at all. [Mailbox OAuth § the two grants](operations/mailbox-oauth.md#the-two-grants) states which applies, and [§ Microsoft — Entra](operations/mailbox-oauth.md#microsoft--entra) is the registration for both. **IMAP is on by default per mailbox, and an administrator can turn it off.** Microsoft documents IMAP4 as enabled when a user mailbox is created, and both the Exchange admin center and `Set-CASMailbox` as the ways to change it — so a refused connection against a tenant that has hardened its protocols is a mailbox setting rather than a credential fault. Sources: [Deprecation of basic authentication in Exchange Online](https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/deprecation-of-basic-authentication-exchange-online), [Managing email apps for user mailboxes](https://learn.microsoft.com/en-us/exchange/recipients-in-exchange-online/manage-user-mailboxes/managing-email-apps-for-user-mailboxes), [Authenticate an IMAP, POP, or SMTP connection using OAuth](https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth). ## Yahoo Mail Yahoo documents `imap.mail.yahoo.com` on port `993` with SSL required, and directs a third-party mail client at a generated app password rather than the account password. That is the ordinary `Secrets:Password` block with the generated value behind the reference, and nothing else about the account changes. This review found no Yahoo documentation of IMAP connection caps, bandwidth ceilings, or the extension set the server advertises, so this page states none — [what your own server actually offers](#what-your-own-server-actually-offers) is where a running deployment answers that instead. Sources: [Yahoo Mail server settings](https://help.yahoo.com/kb/SLN4075.html). ## iCloud Mail Apple documents `imap.mail.me.com` on port `993` with SSL required, and an app-specific password rather than the account password. **The user name is the part that catches people out.** Apple documents it as usually the name portion of the address — `johnappleseed` rather than `johnappleseed@icloud.com` — and says to try the full address if a client cannot connect with the short form. `UserName` is an identifier rather than a secret, so trying the other form is an edit and a reload rather than a re-provisioning. Sources: [iCloud Mail server settings for other email client apps](https://support.apple.com/en-us/102525). ## Proton Mail, through the local bridge This is the one entry whose address is not the mail service. Proton documents that a mail client reaches a Proton mailbox through Proton Mail Bridge, an application that runs on the same machine as the client, holds the connection to Proton itself, and serves IMAP on a loopback address — which is what lets a client see decrypted mail without the decryption happening anywhere else. Proton states that the bridge is available only with a paid Proton Mail plan. Three consequences for a MailFathom account, and the first is the one to settle before writing any configuration: - **The bridge has to be reachable from the process, on loopback.** A deployment in a container or on another host does not reach a bridge running on somebody's laptop, and the bridge is not a service to publish onto a network — its whole design is that the decrypted channel never leaves the device. A Proton mailbox therefore fits a MailFathom process running on the same machine as the bridge, and does not fit the container and Kubernetes shapes without putting the two together deliberately. - **The port and the transport are not the defaults.** Proton documents the bridge's default IMAP port as `1143` and offers a choice of STARTTLS or SSL in the bridge's own settings. STARTTLS on 1143 means the account states `"ConnectionSecurity": "StartTlsRequired"` — which is one of the two guaranteed-TLS modes and needs no `AllowInsecureConnection`, because the handshake either happens or the connection fails. - **The certificate is the bridge's own.** Proton documents the bridge as using a self-signed TLS certificate that it generates when the application is first set up. Certificate validation is never disabled in MailFathom, so the account trusts that certificate as an additional authority: `"CertificateTrust": "AdditionalTrustedAuthority"` with the bridge's certificate behind `TrustedCertificateAuthority`. [Trust anchor material](features/imap-synchronization.md#trust-anchor-material) is what that reference holds and how it is provisioned. The credential is the password the bridge itself generates for the mail client, with the Proton address as `UserName`. It goes behind a `Secrets:Password` reference like any other. ```json { "Host": "127.0.0.1", "Port": 1143, "UserName": "you@proton.me", "TransportSecurity": { "ConnectionSecurity": "StartTlsRequired", "CertificateTrust": "AdditionalTrustedAuthority", "TrustedCertificateAuthority": { "Name": "proton-bridge-certificate", "SecretReference": "file:/etc/mailfathom/secrets/proton-bridge-certificate.pem" } } } ``` Sources: [IMAP, SMTP, and POP3 setup](https://proton.me/support/imap-smtp-and-pop3-setup), [Comprehensive guide to Bridge settings](https://proton.me/support/comprehensive-guide-to-bridge-settings), [Proton Mail Bridge connection issues](https://proton.me/support/bridge-ssl-connection-issue). ## Fastmail Fastmail documents `imap.fastmail.com` on port `993` with SSL/TLS encryption and states explicitly that STARTTLS is not offered there — which is what MailFathom's default `TlsOnConnect` already is, so the account needs no `TransportSecurity` block at all. **The user name is the full address, and it is the one the account was signed up with.** Fastmail documents that other addresses on the same account do not authenticate. **An app password is required.** Fastmail states that the ordinary account password will not connect over IMAP and that each connection needs an app password of its own. Sources: [Server names and ports](https://www.fastmail.help/hc/en-us/articles/1500000278342-Server-names-and-ports), [IMAP, POP, and SMTP](https://www.fastmail.help/hc/en-us/articles/1500000279921-IMAP-POP-and-SMTP). ## Zoho Mail Zoho documents two hostnames on port `993` with SSL required, and which one applies is the account's plan rather than a preference: `imap.zoho.com` for a personal account on a `zoho.com` address, and `imappro.zoho.com` for a paid organization account on its own domain. Zoho runs mailboxes in several data centres and the documented hostnames are the ones for its `.com` domain, so an account elsewhere takes the host its own webmail settings page names rather than the one in the table above. **IMAP is switched on in webmail first.** Zoho documents enabling IMAP access for the account before configuring any client. **Which credential depends on how the account signs in.** Zoho documents the account password for an ordinary account, and an application-specific password where two-factor authentication is on, where the account signs in through SAML, or where it uses a federated sign-in. Either way it is the `Secrets:Password` block. Sources: [IMAP and SMTP configuration details](https://www.zoho.com/mail/help/imap-access.html). ## An IMAP server you run yourself There is no entry to look up here, and that is the point: MailFathom reaches any IMAP server that will serve a TLS connection and an authentication mechanism the account permits, so the server's own documentation is the source and the account block at the top of this page is the whole configuration. Four things are worth checking against it, because each is a startup refusal rather than a runtime warning: - **The port decides nothing; the mode does.** `993` with `TlsOnConnect` is the default. A server serving IMAP on `143` and upgrading takes `"ConnectionSecurity": "StartTlsRequired"`, which is still guaranteed TLS. `StartTlsWhenAvailable` and `None` are not, and each needs `AllowInsecureConnection` written explicitly. - **A private certificate authority is trusted rather than ignored.** `"CertificateTrust": "AdditionalTrustedAuthority"` with the authority behind `TrustedCertificateAuthority`; validation itself cannot be turned off. - **An old server may be refused before a credential is ever sent.** An authentication failure wrapping `SSL Handshake failed with OpenSSL error` is the platform's TLS policy ending the handshake rather than a wrong password. [The platform TLS policy](operations/platform-tls-policy.md) covers confirming that and the one supported way to relax it. - **The mechanism list is an allow-list.** `PermittedAuthenticationMechanisms` defaults to `PLAIN` and `LOGIN`; a server offering something else needs it named. ## What your own server actually offers Nothing above states which IMAP extensions a service advertises, because almost none of them publishes that and a table of guesses would be worse than no table. The running deployment answers it instead, per folder, on every start: ```text Folder primary/inbox is now synchronized in Push mode. ``` Three neighbouring lines say why, when the answer is not the one that was configured: | What the log says | What the server advertised | | --- | --- | | `Account … watches N folders through one push subscription.` | `NOTIFY` and `IDLE`, so the whole account costs one connection | | `… advertises no NOTIFY capability, so each push folder is watched over its own connection` | `IDLE` alone, so each watched folder costs one | | `… the mail server advertises no IDLE capability; it is synchronized by polling` | Neither, so `Mode` changes nothing and the interval is what runs | The same holds for `CONDSTORE` and `QRESYNC`, which decide how much work a reconciliation pass does rather than whether it is correct: all three shapes of the question reach the same end state, and [asking only about what changed](features/imap-synchronization.md#asking-only-about-what-changed) records which command each server gets. There is nothing to configure and nothing to check — a server that supports neither is slower and no less right. ## Whether your server says who sent a message MailFathom would rather not verify a sender itself. What it reads first is the `Authentication-Results` header that the server receiving your mail wrote, because that server is the only party in the chain that observed the connection, saw the sending address the envelope carried, and could evaluate SPF at all. So how well sender verification works is mostly your provider's decision rather than yours, and where that server writes a verdict it is the verdict MailFathom records — nothing on this side is consulted, and nothing on this side overrides it. **Where no such header is found, MailFathom verifies the message's own DKIM signatures for itself**, against the keys the signing domains publish in DNS. That is a fallback rather than a second opinion: it happens only for an account whose server wrote nothing MailFathom trusts, and it establishes a signing domain where there would otherwise be no author to hold `TrustedSenders` or `TrustOwnAccountDomains` against at all. It is what makes those settings work on a mailbox whose provider records nothing, and it is on by default. What it cannot reach is the part that needed the connection: no SPF result, and no DMARC outcome. It is also the one thing in MailFathom that sends a DNS query, so an operator who wants none turns it off — see [the mail configuration](operations/configuration-mail.md#mailsynchronization). Check the mail you were actually delivered rather than the provider's documentation. Open a recently arrived message in any mail client, view its source or full headers, and look for `Authentication-Results`: | What you find | What it means | | --- | --- | | `Authentication-Results: some.host; dkim=pass …` | `some.host`, the first token after the colon, is the authserv-id and is what `TrustedAuthenticationServiceIdentifier` takes. A server may write a version number after it, as `some.host 1;` — that digit belongs to the header rather than to the identifier, and a configured value carrying whitespace fails startup | | Only `ARC-Authentication-Results` | An upstream hop's findings preserved across forwarding, which is a claim a relay signed rather than something your own server observed. It is deliberately never read, and there is nothing to configure from it | | No such header at all | Your server either does not check the sender or does not record what it found. MailFathom then verifies the message's DKIM signatures itself, which recovers the author but not SPF or DMARC | The third case is still worth raising with whoever runs that server, because local verification recovers only part of what a receiving server sees: SPF, DKIM, and DMARC have to be evaluated as mail is delivered, and the outcome written into the message as an RFC 8601 header carrying that server's own identifier. Which software does it, and how it is switched on, is that server's own documentation. An account whose server starts writing the header stops verifying locally on the mail that carries it, without anything being reconfigured. Two things are worth knowing before asking for it. It reaches mail delivered afterwards and cannot reach mail already delivered, because the header is part of the message and nobody can add one to a message that has already arrived. And the verdict is recorded when a message is extracted rather than worked out when it is read, so mail already synchronized keeps the answer it was stored with until [a re-derivation](users/administering.md#filling-in-what-a-newer-version-records) re-reads it — which is also what applies a newly configured authserv-id to the mail you already hold. [Sender authentication](features/sender-authentication.md) states what the verdict records, how the header is chosen, what local verification reaches and deliberately does not, and how a reader tells the two verdicts apart; [the mail configuration](operations/configuration-mail.md#one-account--mailsynchronizationaccountsn) states where the setting lives. ## Related - [Getting started](users/getting-started.md) — the whole path from an installed instance to a first tool call - [Mailbox OAuth](operations/mailbox-oauth.md) — registering an application, obtaining a refresh token, and the `OAuth` block every token-bearing account above takes - [Mail configuration § `MailSynchronization`](operations/configuration-mail.md#mailsynchronization) — every key named here, with its constraint and whether changing it needs a restart - [IMAP synchronization](features/imap-synchronization.md) — what a run actually does with a mailbox - [Secret provisioning](operations/secret-provisioning.md) — how a `SecretReference` is backed by material --- **Trademarks.** The product, service, and company names on this page are their owners' trademarks and are used solely to identify the mail services a MailFathom deployment can be configured against. Their use implies no affiliation with, sponsorship by, endorsement by, or certification from those owners, in either direction, and this page reproduces no third-party logo. Gmail and Google Workspace are trademarks of Google LLC. Microsoft, Outlook, Microsoft 365, Exchange Online, and Microsoft Entra ID are trademarks of the Microsoft group of companies. iCloud is a trademark of Apple Inc., registered in the U.S. and other countries and regions. Yahoo and Yahoo Mail are trademarks of Yahoo Inc. and its affiliate companies. Proton and Proton Mail are trademarks of Proton AG. Fastmail is a trademark and service mark of Fastmail Pty Ltd. Zoho and Zoho Mail are trademarks of Zoho Corporation Private Limited and/or its affiliates. [`THIRD_PARTY_LICENSES.md`](https://github.com/Krzysztof318/MailFathom/blob/main/THIRD_PARTY_LICENSES.md#trademark-and-brand-use) records the per-owner review this statement comes out of, and why it sits here rather than in `NOTICE`. # Administering your deployment MailFathom ships a command, `mfctl`, that talks to a running deployment from your own machine. This page is the user's view of it: what it is for, the path from a running service to a verified sign-in, and what it deliberately cannot do yet. The full contract — every asset name, every stored path, every message the command can print — is [administering a deployment](operations/admin-endpoint.md), and this page links there rather than repeating it. ## What the command is, and is not `mfctl` is a client. Every operation it performs is an HTTP request to the deployment's administrative endpoint, so it runs on the machine you administer *from* rather than the one the service runs on — your laptop against a container on a server, or against a pod in a cluster. It is **not** how MailFathom is configured. Configuration is files and environment variables read at startup, described in [configuration sources](operations/configuration-sources.md); the command never reads them, never opens the database, and never touches the secret store. Nothing you do with it changes what the service will do on its next restart. > **What it does change is the deployment's own state rather than its configuration**: it can place a mailbox > credential, ask for work to be run, dispose of a folder's stored mail, and maintain the contact book. None of that is > a setting, and none of it survives as one — [configuration sources](operations/configuration-sources.md) stays the > only place a deployment's behaviour is decided. **What it prints is meant to be read and safe to capture.** A command's result goes to standard output and everything else to standard error, so redirecting one captures the answer alone, and a redirected run — like any run whose environment sets `NO_COLOR` — carries no escape sequences at all. How a listing and a single record are laid out, and which lines colour marks, is stated in [administering a deployment](operations/admin-endpoint.md) with the rest of the contract. ## Before it can answer The administrative endpoint is off unless a deployment turns it on, and it has credentials of its own. An API key that works against the MCP endpoint authenticates nothing here, deliberately: reading a mailbox and administering the service that reads it are different authorities. ```jsonc { "AdminEndpoint": { "Enabled": true, "BindAddress": "127.0.0.1", "Port": 8090, "Authentication": [ { "ApiKey": { "Name": "workstation", "SecretReference": "systemd-credential:admin-workstation-key" } } ] } } ``` Five things about that block are worth understanding before you copy it: - **It binds a socket of its own.** `127.0.0.1` above is the safe starting point — reachable from the machine the service runs on and nowhere else, which is what an SSH tunnel is for. Publishing it more widely is a decision, not a default. The keys are in the [endpoint configuration](operations/configuration-endpoints.md#adminendpoint), and how it relates to the port your MCP clients use is [where each surface is served](operations/configuration-endpoints.md#where-each-surface-is-served). - **`SecretReference` is a pointer, not a secret.** Where the material actually lives, and how it gets there, is [secret provisioning](operations/secret-provisioning.md). Never write a key into a configuration file. - **A clear-text endpoint is warned about at startup, not refused.** It is the right posture behind a TLS-terminating proxy or on a loopback bind, and the wrong one anywhere else; only you know which you have. Configure `AdminEndpoint:Https:Endpoints` to have MailFathom terminate TLS itself. - **It is rate limited without your writing a number.** An endpoint that answers a network has to bound how fast a caller may present wrong credentials, so the limits apply the moment you enable it and the applied numbers are stated at startup. [Rate limiting](operations/admin-endpoint.md#rate-limiting) is where the settings and the one way this endpoint's limit differs from the MCP endpoint's are recorded. - **A request that runs too long is abandoned, also without your writing a number.** The ceiling defaults to ten minutes because the MCP endpoint shares the setting and an AI-backed answer can legitimately take minutes; no administrative route reaches a provider, so this is the endpoint worth narrowing. [Request timeouts](operations/admin-endpoint.md#request-timeouts) is where that is recorded. ## Getting the command Every release attaches a self-contained binary per platform, with nothing to install beside it — the .NET runtime is inside the file. The asset names and the checksum that tells a genuine download from a tampered one are on [getting the command](operations/admin-endpoint.md#getting-the-command), and on Linux [the install script](operations/admin-endpoint.md#on-linux-with-the-install-script) does the whole of it in one line. Download the one for the machine you administer *from*. The command talks to a deployment over HTTP, so it does not have to run where the service runs — that is the whole point of it being a client. **Take it from the release your deployment is running.** The command and the deployment have to agree on `major.minor`, because a minor release is allowed to change what they say to each other; a pair that does not agree is refused rather than attempted, and a pair that differs only in patch or nightly warns and carries on. [Take the command from the deployment's own release line](operations/admin-endpoint.md#take-the-command-from-the-deployments-own-release-line) is the rule in full. ## Signing in ```console $ mfctl login --endpoint https://mail.example.test:8443 --name production Administrative credential (an API key, or an access token from the configured authorization server): Signed in to https://mail.example.test:8443 as 'workstation' (MailFathom 0.2.0), saved as profile 'production' and selected. ``` The credential is typed at the prompt or piped in, never passed as an argument — an argument reaches your shell history and the process list. It is checked against the deployment before anything is written, so a wrong key, a wrong port, and a host that is not MailFathom all fail here rather than at some later command that leaves you guessing which of the three it was. If the deployment is configured for OAuth, sign in with a browser instead and let it do the authenticating: ```console $ mfctl login --endpoint https://mail.example.test:8443 --mode interactive --client-id mfctl ``` `--mode device` is the same thing on a machine with no browser: it prints a short code to enter on your phone. Either way the only thing you supply is the client identifier — the command asks the deployment where to authorize. Your access token is then renewed for you until the sign-in genuinely ends, and how long that is depends on your identity platform; [how long an OAuth sign-in lasts](operations/admin-endpoint.md#how-long-an-oauth-sign-in-lasts) states the rule and the one setting that shortens it. For a scheduled job there is a third way, and it is the one to prefer there. Generate a key pair, give the deployment the public half only, and sign in with the private one: ```console $ mfctl login --endpoint https://mail.example.test:8443 --mode keypair --private-key ~/.config/MailFathom/production.key ``` Nothing reusable is stored and nothing reusable reaches the deployment: the command signs a fresh credential per request, each good for about a minute, and the only thing the service holds is a public key. [Signing in with a key pair](operations/admin-endpoint.md#with-a-key-pair) has the `openssl` commands and the entry to add. If your deployment serves a certificate your workstation does not trust — self-signed, or issued by an authority only your organization carries — the sign-in shows you that certificate and asks once whether to trust it, the way an SSH client asks about a host key: ```console $ mfctl login --endpoint https://mail.internal.example:8443 --name internal … Fingerprint: 3B:9A:1C:…:7F Trust this certificate for this profile? [y/N]: ``` Compare the fingerprint against the deployment's own before you answer; nothing has been sent yet. Saying yes stores that fingerprint on the profile, which makes the profile **stricter** rather than looser: from then on it accepts that one certificate and refuses every other, so a renewal or a substitution stops the profile rather than passing unnoticed. You accept a renewed certificate by signing in again. An `http://` address gets a question of its own, because the credential and every later request would cross the network in clear text and a redirect to `https://` arrives too late to change that. [When the connection is weaker than the default](operations/admin-endpoint.md#when-the-connection-is-weaker-than-the-default) has both questions in full, and the two switches a scripted sign-in states the answers with. What is stored afterwards is one small file per user, with the tokens encrypted and the key beside it — and for a key-pair profile, no credential at all; [where the credential is kept](operations/admin-endpoint.md#where-the-credential-is-kept) states the paths, what the encryption does protect, and what it does not. ## Everyday use | What you want | Command | | --- | --- | | See which deployments you are signed in to | `mfctl profiles` | | Work against a different one from now on | `mfctl switch staging` | | Work against one just this once | any command with `--endpoint staging` | | Check whether your credential still works | `mfctl status` | | Forget a deployment on this machine | `mfctl logout` | Two of those repay a second look. `status` asks the *deployment*, which is what tells a revoked or expired key apart from a host that is simply down — the stored profile can only say what was true when you signed in. And `logout` forgets a local profile without revoking anything: the credential keeps working until the deployment stops accepting it, so a lost laptop is a reason to rotate the key on the server rather than to sign out. `status` also prints what your credential may do, which is what decides whether any other command here will work: ```console $ mfctl status 'production' (https://mail.example.test:8443) accepts the stored credential as 'workstation' (MailFathom 0.2.0). It holds mailfathom.admin.read, mailfathom.admin.operate. Documentation for that version: https://krzysztof318.github.io/MailFathom/v0.2.0/ ``` A credential is granted a set of named permissions on the deployment, and each command needs the one its operation is published under — two, for the six commands that read something before they change it. Signing in needs none, so a key that reads `It holds no administrative permission` still signs in and is refused everywhere else — which is how a credential is retired without its entry being removed. When a command is refused for want of one, it names the permission to add and where it is written, so the answer is to widen that credential's grant rather than to replace the key. [What a credential may do](operations/permissions.md) lists the names, what each covers, and which six commands need a second one; [what the endpoint serves](operations/admin-endpoint.md#what-the-endpoint-serves) names the permission every route is published under. When you work against one deployment for a whole session, `MAILFATHOM_ENDPOINT` states it once for the shell. `--endpoint` beats it, and both beat the profile you last switched to. Every command exits `0` when it did what you asked and `1` when it did not, having explained itself on standard error first. [The troubleshooting table](operations/admin-endpoint.md#troubleshooting) reads each message back to you as a cause. Each invocation also leaves a line in `~/.config/MailFathom/mfctl.log` — what ran, against which of your profiles, how long it took, and how it ended — so a command you ran yesterday is still answerable today once the scrollback is gone: ```console $ tail -3 ~/.config/MailFathom/mfctl.log | jq -r '"\(.at) \(.command) \(.outcome)"' ``` No credential and no mail goes into it, it is bounded so it cannot fill a disk, and `--no-log` leaves one invocation out. What it does name is your own deployment — the profile, and the address where a failure message quoted the one you typed — so read it before you paste it anywhere, the way you would the credentials file beside it. [What the command records about itself](operations/admin-endpoint.md#what-the-command-records-about-itself) has the path on each platform, every field, and the switch that turns it off for a whole session. ## Authorizing a mailbox A mailbox at a provider that no longer accepts a password — a Google Workspace account, anything on Exchange Online — needs a person to sign in once before MailFathom can read it. A headless service cannot arrange that, so the command does it: ```console $ mfctl mailbox authorize --provider google --client-id ``` This is the one command that talks to something other than your deployment, and it is why running `mfctl` on your own computer matters. It listens on a loopback address, opens your browser, and catches the redirect the provider sends back — so there is nothing to copy, and the authorization code never crosses a network. On a machine with no browser, forward the port over SSH, or use `--mode device` for Microsoft and `--mode manual` for Google. What it produces is a refresh token, and `--account` decides where that token goes: ```console $ mfctl mailbox authorize --provider google --client-id --account workspace … Stored the refresh token for account 'workspace' on 'production'. It was not printed. ``` **Named, the token goes straight to your deployment**, which encrypts it and keeps it. It is never printed, so it never reaches your scrollback or a session log, and there is nothing for you to place by hand. The account has to be one your deployment configures; a name it does not know is refused and nothing is stored. Re-running it replaces what was stored, which is what re-authorizing after a revocation is. **Omitted, the token is printed on standard output and you provision it on the server yourself**, as a secret reference like every other credential. That is what a deployment with no administrative endpoint needs, and it stays the right choice if you would rather place credentials yourself. [Mailbox OAuth](operations/mailbox-oauth.md) is the whole procedure, including what each provider requires of the application you register and how a stored token relates to the reference in your configuration. ## Finding out why mail is not arriving A mailbox that looks empty and a mailbox nothing is fetching look identical from the outside. This is the command that tells them apart: ```console $ mfctl mailbox status ``` It reports, for every account your deployment configures and every folder it maps, what the deployment is doing right now, how its last run ended, how far each folder has actually got, and when that last moved. The reading worth learning is the pair of columns in each account's folder table. **Progress** is how far the deployment has durably got and when it last got there; **Last run** is what happened the last time it tried. A folder whose progress stopped yesterday and whose last run succeeded has nothing left to fetch. A folder whose progress stopped yesterday and whose runs keep ending has stopped making headway, and the outcome beside it says why — an alias naming no folder your server advertises, a server that stopped answering, or a failure to look up in the log. Without both columns the two are indistinguishable, which is exactly the situation this command exists to end. The account's own readings above that table say whether a run is happening now, queued behind other accounts, or waiting; and, when runs have been failing, how many in a row — which is what a wait far longer than your configured interval is explained by. [Administering a deployment](operations/admin-endpoint.md#reading-what-synchronization-is-doing) reads every line of the output back to you. ## Turning semantic search on Which model your deployment embeds with is a configuration value, and editing it starts nothing. Turning semantic search on is a separate, deliberate act, because it is the first thing MailFathom does that costs money per unit of mail — a full mailbox goes to your provider, and you should see the size of that before agreeing to it: ```console $ mfctl embedding activate ``` The command reads what the deployment would spend, prints it as passages, characters, and approximate tokens, and asks before it starts. `--yes` agrees up front, for a scripted run. | What you want | Command | | --- | --- | | Find out why semantic search is quiet | `mfctl embedding status` | | Take up the model your configuration declares | `mfctl embedding activate` | | Stop a re-embed you have changed your mind about | `mfctl embedding cancel-reindex` | `mfctl embedding status` is the one to reach for first. It answers, in one output, whether a model is active, whether that model is still the one your configuration declares, whether your provider is answering, how much of the mailbox is embedded, what the current budget period has spent, and when the walk that embeds your existing mail next runs. That last line is the one to read in the minutes after an activation: until the first passages have gone out, a deployment that is simply between passes looks exactly like one that is broken. [Administering the embedding profile](operations/admin-endpoint.md#administering-the-embedding-profile) is the operator's reference for all three, and [changing the embedding model](operations/embedding-profiles.md) is what a switch and a rollback cost. ## Applying your rules, and seeing what they did [Mail rules](features/mail-rules.md) select mail as it arrives, so a rule you write today does nothing about the mail already in the mailbox until you ask — or until a [schedule](features/mail-rules.md#running-a-rule-on-a-schedule) you gave the rule asks on your behalf. That asking is a command, and so is finding out what the rules have been doing: | What you want | Command | | --- | --- | | See which rules your deployment is running, in order | `mfctl rules list` | | Read one of them in full | `mfctl rules show file-invoices` | | Apply them to mail that arrived before them | `mfctl rules run --account work` | | Watch that run | `mfctl rules run-status --account work` | | Find out what a rule did, or why a message is where it is | `mfctl rules history --account work` | `mfctl rules list` is the one to run after editing a rule file. A deployment refuses a reload whose rules do not validate and goes on running the previous set, which it reports to its log and nowhere else — so this is where you find out whether your edit took effect, rather than from mail that kept being filed the old way. It prints one row per rule, and the `Runs on` column says what runs each of them, which is how a rule naming [no trigger](features/mail-rules.md#which-triggers-run-a-rule) is told apart from one that simply never matched: nothing fires such a rule by itself, and `mfctl rules run` is how it is run. A rule with a schedule says so in that same column, with the occasions it declares beside the trigger. It is also where a rule you meant to run over arriving mail shows that it never says `Arrival`. None of these writes a rule, and none ever will: rules are configuration, so you change one by editing the file your deployment reads. [Reading the rules, running them, and finding out what they did](operations/admin-endpoint.md#reading-the-rules-running-them-and-finding-out-what-they-did) is the operator's reference for all five, including what the history records and what it deliberately does not. ## Classifying the mail you already have [Spam classification](features/spam-classification.md) reaches arriving mail on its own: a message is classified because it arrived, and you ask for nothing. What it does not reach is the mail that was already there — everything stored before you switched it on, and everything stored while it was off — so switching it on, switching filing on, or moving a threshold does nothing about the existing mailbox until you ask: | What you want | Command | | --- | --- | | Find out what classification would do to the mail you have | `mfctl spam run --account work` | | Carry that out | `mfctl spam run --account work --apply` | | Watch the run | `mfctl spam run-status --account work` | | Find out why a message was filed as junk | `mfctl spam classifications --account work --email ` | **The run is a dry run unless you add `--apply`**, and that is the order to do it in: run it, read what it found with `run-status`, and only then run it again with `--apply`. With filing switched on, a run over an inbox is the largest single thing MailFathom does to your mail, and the dry run tells you how much of it would move before any of it does. The verdicts are recorded either way — what `--apply` adds is the mail server being written to. A second run while one is going does not start a second walk; you are told the first is still under way, on the terms it was started with. None of these writes a setting, and none ever will: whether mail is classified, what a scanner is judged by, and what happens to junk are configuration, so you change them by editing the file your deployment reads. [Classifying the mail you already have, and reading what was concluded](operations/admin-endpoint.md#classifying-the-mail-you-already-have-and-reading-what-was-concluded) is the operator's reference for all three. ## Getting a folder's storage back MailFathom never takes local mail away because a file changed. Switching a folder's `Synchronize` off stops mirroring it and keeps what it already stored; removing its mapping leaves those rows where they are as well. Both are the right default — an edit to a configuration file should not dispose of mail — and both leave you with storage you may actually want back: ```console $ mfctl folder erase --account work --folder archive 1043 stored emails erased from ARCHIVE under work. The folder holds none, and its checkpoint went with them, so mirroring it again starts from the beginning rather than resuming. ``` This is the only command that erases mail, and it acts on one folder of one account. It refuses a folder your deployment still mirrors, because the next synchronization run would simply fetch it all again — switch that folder off, or take its mapping out, and ask again. A folder whose mapping you already removed is accepted, which is the case this exists for. Erasing a large folder takes a while and the command prints how far it has got. Stopping it is safe: what it reported erasing is gone, the rest is untouched, and running the same command again continues from there. [Erasing a folder you have stopped mirroring](operations/admin-endpoint.md#erasing-a-folder-you-have-stopped-mirroring) is the operator's reference, including what goes with the mail and what survives it. ## Filling in what a newer version records An upgrade sometimes teaches MailFathom to record something new about a message — who was established to have sent it, which keywords its folder carries. New mail gets it because it arrives after the upgrade. **The mail you already have does not**, and nothing fills it in on its own: synchronization resumes from where it left off in each folder, so a message it has already stored is never looked at again. | What you want | Command | | --- | --- | | Fill in what is already in the mail you stored | `mfctl mailbox rederive --account work` | | See how far that has got | `mfctl mailbox rederive-status --account work` | | Fill in what only your mail server knows | `mfctl mailbox rewind --account work` | | Any of them, for a single folder | add `--folder archive` | **Reach for `rederive` first, because it is nearly free.** Anything the message itself carries is in the raw mail already on your server's disk, so filling in the column means reading that back and parsing it. Nothing is fetched, your mail server is not contacted at all, and nothing is marked read. **The command asks for the re-reading and returns; the deployment does it.** Your terminal is not what keeps it going, so closing it changes nothing and the run survives a restart of the deployment: ```console $ mfctl mailbox rederive --account work A re-derivation of every folder under work has been asked for. Requested: 2026-08-18 12:00:00Z Progress: 0 re-read, 0 unparseable, 0 no longer stored The deployment carries the run in the background. Watch it with 'mfctl mailbox rederive-status --account work'. ``` Asking again while one is going is answered with the run already under way rather than starting a second, so a command you are not sure landed is safe to repeat. How far it has come is a second command: ```console $ mfctl mailbox rederive-status --account work Scope: every folder under work — under way Requested: 2026-08-18 12:00:00Z Progress: 1,043 re-read, 0 unparseable, 0 no longer stored If it stops moving, look for the work that stopped with 'mfctl jobs dead-letters'. ``` **`rewind` is the one to be careful with.** Some things are your mail server's answer rather than the message's — flags, keywords, the date it received the message — and the only way to learn them is to ask for the mail again. So `rewind` forgets how far synchronization has got, which makes the next runs fetch the whole mailbox over again. It tells you how much that is and asks before it does anything: ```console $ mfctl mailbox rewind --account work Scope: every folder under work Cost: 22,500 stored emails would be fetched from the mail server, re-read, and stored again. Rewind that scope? [y/N] ``` Say no and nothing changes. Add `--yes` if you are scripting it and there is nobody to answer. **Neither command deletes anything, and neither creates duplicates.** Your mail, its attachments, and everything built from it stay where they are; mail fetched again lands on the message that is already there. Neither one re-embeds anything either, so no refresh can run up a bill with your AI provider. Both take a while on a large mailbox. `rewind` prints how far it has got as it goes, because the command is what does it; `rederive` returns at once and `rederive-status` is where its progress is read. Neither loses what it has already done: a deployment restarted mid-re-derivation picks the run up where it stopped, and mail it had already re-read is not re-read again. [Bringing stored mail up to a later release](operations/admin-endpoint.md#bringing-stored-mail-up-to-a-later-release) is the operator's reference for all three. ## Background work that stopped MailFathom does most of what it does in the background: classifying a message, embedding a passage, carrying out what a rule asked for. A piece of that work that keeps failing is eventually given up on rather than retried forever, and what is left behind is called a *dead letter* — a record of work nobody will attempt again. Nothing waits on one, so nothing tells you it is there: | What you want | Command | | --- | --- | | See what has stopped | `mfctl jobs dead-letters` | | See what has stopped for one account | `mfctl jobs dead-letters --account work` | | Run one again, after fixing what broke it | `mfctl jobs retry --job ` | | Decide one will never run | `mfctl jobs drop --job ` | ```console $ mfctl jobs dead-letters Stopped Job Kind Failed Work Queued 2026-08-13 09:30:00Z 0199c3d0-0000-7000-8000-000000000002 classify-email-spam Permanent PayloadUnreadable after 5 attempt(s) account:work|email:0199c3d0-0000-7000-8000-000000000001 for work 2026-08-13 09:00:00Z Run one again with 'mfctl jobs retry --job ', or write it off with 'mfctl jobs drop --job '. ``` **`Failed` is what decides which of the two commands is right.** `Permanent` names something that will fail the same way every time — a credential, a setting, a message the deployment cannot read — so retrying before you have changed something achieves nothing. `Transient` names a dependency that stayed broken for longer than the queue was willing to wait, which is the case `retry` exists for: fix it, or wait for it to come back, and ask again. `retry` runs the same piece of work rather than enqueuing a second one, so it is safe on work that reaches your mailbox. `drop` deletes nothing — the record stays, keeping what stopped it, and it goes on being the reason the same work is not enqueued again. Neither command waits for anything: the deployment writes the decision down and the next worker to come along carries it out, so closing the terminal changes nothing. An empty reading is the ordinary state of a healthy deployment, and it says so rather than printing nothing. [Reading the background work that stopped, and deciding what becomes of it](operations/admin-endpoint.md#reading-the-background-work-that-stopped-and-deciding-what-becomes-of-it) is the operator's reference, and [durable background work](operations/telemetry.md#durable-background-work) is what your monitoring can watch instead of you reading this by hand. ## Mail you asked it to send Sending is the one thing MailFathom does that somebody else notices going wrong before you do. A message that will not leave waits quietly: nothing retries it forever, nothing pages you, and the first sign is usually a person asking why they never heard back. The `outbox` commands are how you look: | What you want | Command | | --- | --- | | See whether anything is stuck | `mfctl outbox status` | | See what is queued, newest first | `mfctl outbox list` | | See only what is waiting | `mfctl outbox list --stage Recorded` | | See who one message is for, and what their server said | `mfctl outbox show --message ` | | Take one back before it leaves | `mfctl outbox cancel --message ` | | Offer one again | `mfctl outbox requeue --message ` | ```console $ mfctl outbox status Stage Messages Recorded 2 TransmissionBegun 1 Sent 418 Refused 3 Cancelled 0 3 message(s) are still waiting. See which with 'mfctl outbox list'. ``` **The stage is what tells you which command is right.** `Recorded` is a message waiting for its next attempt — leave it alone unless it stops moving, and it is the only stage `cancel` applies at. `Sent` and `Cancelled` are finished. `Refused` is a message a server will not take, so offering it again is a decision to disbelieve that and the command makes you say so: `mfctl outbox requeue --message --despite-refusal`. `TransmissionBegun` is the one that needs you. The message began to go out and the server never answered, so **nobody knows whether it arrived** — and MailFathom will not guess on your behalf, because attempting it again might put a second copy in somebody's mailbox rather than fix a failure. Read it with `outbox show`, decide, and then either `requeue` it or leave it where it is. `cancel` means the message reached nobody: the deployment refuses to withdraw one that has begun transmitting rather than racing the worker sending it. `requeue` gives the attempts back and offers the message only to the addresses still outstanding, so nobody a server already accepted it for gets it twice. Neither command waits for anything — the deployment writes the decision down and the next delivery pass carries it out. `mfctl outbox list` names no recipient and no subject, deliberately: a listing of who you write to and when is not something to leave in a terminal. Ask `outbox show` about a message when you need to know who it was for. [Reading what is in the outbox, and deciding about one message](operations/admin-endpoint.md#reading-what-is-in-the-outbox-and-deciding-about-one-message) is the operator's reference, and [what delivering the outbox emits](operations/telemetry.md#what-delivering-the-outbox-emits) is what your monitoring can watch instead of you reading this by hand. ## Keeping your contact book MailFathom holds a contact book of its own — people, the addresses each of them uses, and what you wrote about them — and `mfctl contact` is where you keep it: ```console $ mfctl contact create --name "Anna Kowalska" --address anna@example.test $ mfctl contact add-address --id 018f2b1c-9b3a-7c41-8f7d-2c6a5e9d10ab --address a.kowalska@work.example $ mfctl contact show --address a.kowalska@work.example ``` A contact is a *person* rather than an address, which is why the third command answers with Anna rather than with a match: one person uses a work address, a personal one, and an old one they still receive on, and the book knows those are the same person. [Contacts](features/contacts.md) is what the record holds and every rule it obeys. Two of the commands are not conveniences. **`mfctl contact delete` erases somebody** — the record and their addresses go from the database and nothing can put them back, so the command shows you the record and asks first. **`mfctl contact export` writes everything held about a person** as JSON on standard output, which is what you redirect into a file and hand to somebody who asked what you have about them. Those are the two things you will need on a day when somebody asks, and they are commands so that you are not assembling either by hand on that day. Listing is paged on purpose: your contact book is other people's personal data, so there is no command that prints all of it in one go. `mfctl contact list` reads a page and prints the cursor for the next one. [Administering the contact book](operations/admin-endpoint.md#administering-the-contact-book) is the operator's reference for every command, option, and refusal. **The book can also fill itself, and it does not until you say so.** Switching [contact collection](features/contacts.md#collecting-contacts-from-arriving-mail) on for an account records the people that account corresponds with as its mail is synchronized. Those records are the deployment's rather than yours: `mfctl contact promote` is how you take one on, and every other command works on it afterwards. If you change your mind about the whole thing, `mfctl contact delete-collected` erases everything it collected and keeps everything you entered — and switching collection off in configuration is the separate act that stops the book filling again. ## Where to go next - [Administering a deployment](operations/admin-endpoint.md) — the operator's reference for everything above - [Contacts](features/contacts.md) — what the contact book holds, and every rule a writer of it obeys - [Mail rules](features/mail-rules.md) — every fact, operator, and action a rule can use - [Mailbox OAuth](operations/mailbox-oauth.md) — registering the application, and every mode of the sign-in above - [Changing the embedding model](operations/embedding-profiles.md) — what activating, switching, and rolling back cost - [Endpoint configuration](operations/configuration-endpoints.md#adminendpoint) — every `AdminEndpoint` key - [Secret provisioning](operations/secret-provisioning.md) and [rotation](operations/secret-rotation.md) — how the key on the server is supplied and replaced --- Copyright © 2026 Krzysztof Kasprowicz Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. Project repository: https://github.com/Krzysztof318/MailFathom