Mail-Archiver: a PostgreSQL mail archive, and two compose files that disagree
Mail-Archiver is a web application for archiving, searching, and exporting emails from multiple accounts. Featuring folder sync, attachment support, mailbox migration and a dashboard.
At a glance
- What is it?
- A .NET 10 application that pulls mail from IMAP, Microsoft 365 and personal Microsoft accounts into PostgreSQL, deletes the server copy on a schedule, and opens read-only REST and MCP doors into the result. Its Quick Start and its committed compose file disagree about the image, the database and the volumes.
- Who is it for?
- Mail-Archiver fits an administrator who has to find and export mail after the server has moved on, and it fits a host where a reverse proxy is already a given. It does not fit a team that needs TLS from the application itself, outbound sending, or an archive that never leaves the mailserver.
- Can I use it commercially?
- Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly C#, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One PostgreSQL store behind search, export and a storage dashboard
Mail-Archiver exists to cover the gap between a live mailbox and a folder of exported files. Mail servers delete on their own schedule, and the person who later has to answer what arrived in March can no longer ask the server. This project copies mail out of connected accounts into PostgreSQL, indexes it for filtered search, and gives the collection back through three doors: a browser UI, export of a complete account or a selected set as mbox or zipped EML, and read-only programmatic access.
Its user is an administrator rather than an inbox owner. Multi-user support carries account-specific permissions, so one account holder can see one mailbox and not another. A local administrator credential is supplied through environment variables, and OpenID Connect is available for deployments that already run an identity provider. A dashboard shows statistics and storage monitoring, which matters when the reason for keeping the copy is simply that there may be room for it.
Two things frame the rest of this article. The README calls its documentation still fresh and continuously expanding, so some configuration detail lives outside the README proper, in the guides indexed from doc/Index.md. And the repository carries both a Dockerfile and a docker-compose.yml, meaning the published image and a source build are both on the table, with real differences between them. The repository is not archived; its last push landed on 2026-09-28 and release 2609.1 shipped on 2026-09-22.
IMAP, M365, Microsoft Personal and IMPORT are four different things
The four account types are not four flavours of one connection, and picking the wrong one wastes setup time. IMAP accounts synchronise in full and are the general path. M365 accounts reach Microsoft 365 through the Microsoft Graph API and require an Azure app registration, which has its own guide. Microsoft Personal accounts cover Outlook.com, Hotmail, live.com and M365 Family through OAuth2 Device Code Flow, and this type arrives with a pre-registered shared Client ID, so no Azure App Registration is needed. IMPORT accounts are import-only, made for bringing an existing collection in rather than tracking a live server.
The IMPORT type changes what the deployment is. A team holding mbox files, or a mail server being switched off, has no reason to hand over ongoing IMAP access, and this account type is the one built for that case. What it gives up is obvious: it will not pick up anything new afterwards.
The Microsoft Personal path carries a detail worth settling before you commit to it. The README says the shared Client ID ships with the application, and says nothing about whether that ID is unique per installation. An administrator whose tenant restricts which applications may sign users in will want that answered before onboarding a large number of personal accounts.
The deletion lock blocks manual deletes but not retention
Retention is where an archive tool starts deleting things, and Mail-Archiver splits the two directions apart. The copy held on the mailserver can be removed after a configurable period, set per account, with 30, 90 and 365 days given as examples. The local archive carries its own separate retention setting, so mail can stay in PostgreSQL long after it has vanished from the server.
A compliance control is offered under the name deletion lock, switched on with `DeletionPolicy__DeletionAllowed=false`. Its reach is narrower than its label. That setting disables manual deletion of stored mail, and the README is explicit that local retention deletion remains exempt and still runs. An administrator who reads the label as an append-only store will be wrong on the second point: nothing can be removed by hand, yet the retention job keeps emptying the archive on schedule.
What is left for anyone treating the collection as evidence is the access log, which records activity types including Login, Opening, Searches and Exports, and the export path itself, which produces a whole account as mbox or zipped EML before anything is removed.
The Quick Start compose file is not the compose file in the repo
Install the image from the Quick Start and you are running a different setup from the one committed to the repository. The README snippet pulls the published image `s1t5/mailarchiver:latest`, while the repository's docker-compose.yml builds from source with `build: .`. The snippet makes the application wait for a healthy database through a `service_healthy` condition, while the committed file lists postgres as a plain dependency with no condition at all.
services:
mailarchive-app:
image: s1t5/mailarchiver:latest
restart: always
environment:
# Database Connection
- ConnectionStrings__DefaultConnection=Host=postgres;Database=MailArchiver;Username=mailuser;Password=masterkey;
# Authentication Settings
- Authentication__Username=admin
- Authentication__Password=secure123!Those lines are the starting point the Quick Start asks you to copy, with a sample database connection and a sample administrator credential that both have to be replaced.
The database service differs just as clearly.
postgres:
image: postgres:14-alpine
restart: always
environment:
POSTGRES_DB: MailArchiver
POSTGRES_USER: mailuser
POSTGRES_PASSWORD: masterkeyQuick Start pins PostgreSQL 17 where the committed file pins PostgreSQL 14, a full major version apart, and that choice matters the first time you restore a backup taken on the other one. Both files carry the same `pg_isready` healthcheck, polling every 10 seconds with 5 attempts after a 10 second start period, yet only the Quick Start version consumes it, through the dependency condition above.
Beyond the tags, the committed file mounts `./appsettings.json` over the application config and `./logs` for log output, and it carries a commented volume line for a host directory of mbox files, with a note to add the path to `LocalImport.AllowedPaths` in appsettings.json. The Quick Start mounts only the data protection keys, and it opens with no version key at all, where the committed file starts with `version: '3.8'`. A local import that fails after you follow the Quick Start is a mount that was never in the snippet.
Port 5000 speaks plain HTTP, so a reverse proxy is required
A first run is short, provided you accept that a reverse proxy goes in front of it. The image sets `ASPNETCORE_URLS` to http://+:5000, exposes port 5000, and the Quick Start says in plain terms that the application itself does not provide encrypted access via https.
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
# Install Kerberos library required by MailKit (libgssapi_krb5.so.2)r
RUN apt-get update \
&& apt-get install -y --no-install-recommends libgssapi-krb5-2 \
&& rm -rf /var/lib/apt/lists/*That Kerberos package is the only system dependency the image installs, and it is there for MailKit talking to a domain-joined server. The build stage pulls the matching .NET 10 SDK image, so a source build needs nothing else installed on the host, only Docker and Docker Compose.
Then write the compose file, edit `POSTGRES_PASSWORD` and the connection string so they match, set `Authentication__Username` and `Authentication__Password` for the administrator, and start the containers.
docker compose up -dOpen the mapped port in a browser, sign in with the credentials you just set, then add the first account under Email Accounts. Later restarts use the same file with a different verb.
docker compose restartUpgrading goes no further than that restart verb. The README gives no procedure for moving an existing archive onto a newer database, no schema migration step and no rollback, which is why the PostgreSQL major version you pick at the start is the one you will be living with.
One setting deserves a second look. The Quick Start tells you to adjust `TimeZone__DisplayTimeZoneId`, whose default is written as Etc/UCT, while the Dockerfile sets a container-wide TZ of UTC. Those two strings are not the same, so set the variable yourself to an IANA identifier such as Europe/Berlin or Asia/Tokyo instead of trusting the default.
Read-only API keys and five MCP tools for agents
Two of the three doors are meant for machines, and both are one-way by design. The read-only REST API (v1) hands out scoped per-user API keys, so a script can query the store without ever holding a mailbox password. The MCP Server sits behind an optional Streamable HTTP endpoint at `/mcp` and presents the same read-only collection to AI agents as discoverable tools: `search_emails`, `get_email`, `list_accounts`, `list_folders` and `get_attachment`.
Repository layout backs that up: a top-level Mcp/ directory sits beside the controller, service, model and migration folders, and the detail lives in doc/API.md and doc/MCP.md rather than in the README.
What the README leaves open is the operational half of both doors. The lifetime of an API key, any rate limit on the endpoint, and the way a key gets revoked do not appear there, so an operator wiring an agent to this store has to settle those questions before exposing it. The read-only stance is the part that pays off: an agent holding those five tools cannot delete a message or rewrite an account, which is not the case for most mail integrations an assistant might otherwise be pointed at.
Tenant import, mbox restore and mailbox copy are different jobs
Three separate operations move mail around inside the product, and they are easy to confuse. M365 Tenant Import takes a tenant and pulls in its mailboxes from a single form, either all of them or a selection, skipping accounts that already exist and accounts that are disabled. MBox and EML (ZIP) import handles collections with folder structure, and restore brings back either individual emails or an entire mailbox. Mailbox Migrations copy mail from one mailbox to another while preserving folder structure, which saves exporting and re-importing by hand.
The honest alternative to all of this is a directory of mbox files on a file server. It costs less, and it keeps the bytes exactly as the server produced them. What it cannot do is what Mail-Archiver does with them: index messages for filtered search across accounts, hand a named user a scoped key instead of a directory permission, or reach back to the mailserver and remove the source copy when retention fires.
That reach-back is also why this is the wrong tool in some environments. A site that forbids message bodies in a database, or a team that wants the same application to send outgoing mail, needs a different product: the README covers archiving, import, restore, migration and export, and lists no sending function at all.
Editorial conclusion
Mail-Archiver fits an administrator who has to find and export mail after the server has moved on, and it fits a host where a reverse proxy is already a given. It does not fit a team that needs TLS from the application itself, outbound sending, or an archive that never leaves the mailserver. Before the first start, diff the Quick Start compose file against the committed docker-compose.yml, pick the PostgreSQL major version on purpose, and decide between the published image and a source build, then check which of the two your restore plan assumes.
Frequently asked questions
Does Mail-Archiver need a reverse proxy for HTTPS?
Yes. The Dockerfile sets ASPNETCORE_URLS to http://+:5000 and the Quick Start says the application itself does not provide encrypted access via https, so a reverse proxy of your choice with https has to sit in front of the port mapping.
How do I install Mail-Archiver with Docker?
The Quick Start gives a docker-compose.yml with the application on port 5000 and a PostgreSQL service beside it. You edit POSTGRES_PASSWORD and the connection string so they agree, set Authentication__Username and Authentication__Password for the admin user, then start the containers with docker compose up -d.
Why does the docker-compose.yml in the repository differ from the one in the README?
The README snippet pulls the published image s1t5/mailarchiver:latest and pins postgres:17-alpine, while the committed docker-compose.yml builds from source with build: . and pins postgres:14-alpine. The committed file also mounts appsettings.json and logs, which the snippet does not.
Can AI agents read a Mail-Archiver archive?
The optional MCP Server, a Streamable HTTP endpoint at /mcp, exposes search_emails, get_email, list_accounts, list_folders and get_attachment to agents, all read-only. The REST API (v1) covers the same ground for scripts through scoped per-user API keys.
Will Mail-Archiver delete mail from my mail server?
Yes, a configurable retention period set per account, with 30, 90 and 365 days given as examples, removes the copy held on the mailserver, while the local archive has separate retention. Setting DeletionPolicy__DeletionAllowed=false blocks manual deletion, and local retention deletion is exempt and still runs.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/s1t5-mail-archiver)Was this useful?