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; a developer evaluating from the checkout can run the Aspire orchestration instead, which provisions PostgreSQL and applies the schema on its own.
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 lists every key used below.
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 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 is the whole of it. - An MCP API key for the client that will connect. Generate it rather than inventing it:
openssl rand -base64 33 | tr -d '\n' > mcp-workstation-key
Where the files go is the deployment's convention: secrets/mailfathom/ for
Compose, a Kubernetes Secret mounted at
/etc/mailfathom/secrets for the chart, LoadCredential=
for systemd. 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:
{
"MailSynchronization": {
"Enabled": true,
"Accounts": [
{
"AccountId": "primary",
"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" }
]
}
]
}
}
Points worth knowing before you adapt it:
AccountIdandAliasare 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.- Folders are best named by role.
SpecialUselets discovery find the folder whatever the server calls it — a German server'sGesendetis stillSent— and configuring no folder at all synchronizes the inbox. Naming an exact server path is the alternative for folders with no role. - 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 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 covers how to confirm that and the one supported way to relax it. - How far back to synchronize is per account:
EarliestEmailReceivedDatebounds 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 theNOTIFYextension, one per watched folder where it supports onlyIDLE, 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 itsInterval, and a notification only starts the next run sooner. Push synchronization records the whole model.
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.
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:
{
"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:
The database has not applied 1 migration(s) this build defines: 20260731132336_Initial.
That is the explicit schema step described in installing MailFathom: 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:
scripts/build-schema-artifact.sh # artifacts/schema/mailfathom-schema-<version>.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 explains along with the grants that leaves behind.
5. Verify it is healthy
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 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:
{
"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 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.
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.
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 <the key material> |
A connected client's tool listing should show exactly three tools — list_emails, get_email_content,
search_emails — each advertising itself as read-only, non-destructive, and idempotent.
Verifying an enabled endpoint is the checklist form of
this, including what the refusals look like when the key or the origin is wrong.
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:
folderFreshnesscarries one entry per folder in scope, stating when synchronization last committed progress there. An empty page whose entries reportwasSynchronized: falsemeans the folder has not synchronized yet — not that the mailbox is empty. This is the field to check before trusting any early result.nextCursoris 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 describes the day-to-day surface: what each tool answers, what it bounds, and
what its errors mean, and administering your deployment covers reaching the running service from
your own machine with the mfctl command.