🛠️ Mail Archiver Setup Guide¶
📋 Overview¶
This guide provides detailed instructions for setting up the Mail Archiver application using Docker Compose.
🛠️ Prerequisites¶
🚀 Installation Steps¶
-
Install the prerequisites on your system.
-
Create a
docker-compose.ymlfile with the following content:
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!
- Authentication__SessionTimeoutMinutes=60
- Authentication__CookieName=MailArchiverAuth
- Authentication__CookieSameSite=Lax
# REST API Settings (read-only, disabled by default)
- Api__Enabled=false
- Api__AllowAttachmentDownloads=true
- Api__EnableSwaggerUi=true
- Api__DefaultPageSize=20
- Api__MaxPageSize=100
- Api__RateLimitPerMinute=120
# MCP Server Settings (Model Context Protocol for AI agents, read-only, disabled by default)
- Mcp__Enabled=false
- Mcp__AllowAttachmentDownloads=true
- Mcp__DefaultPageSize=20
- Mcp__MaxResults=100
- Mcp__MaxAttachmentBytes=10000000
- Mcp__RateLimitPerMinute=120
# MailSync Settings
- MailSync__IntervalMinutes=15
- MailSync__TimeoutMinutes=0
- MailSync__ConnectionTimeoutSeconds=180
- MailSync__CommandTimeoutSeconds=300
- MailSync__AlwaysForceFullSync=false
- MailSync__IgnoreSelfSignedCert=false
- MailSync__MaxConcurrentSyncs=1
- MailSync__InterAccountDelaySeconds=0
- MailSync__MaxIssuesPerKind=20
- MailSync__FullSyncIntervalHours=24
- MailSync__ExcludeSubfolders=false
- MailSync__GlobalExcludedFolders__0=Calendar
- MailSync__GlobalExcludedFolders__1=Contacts
# BatchRestore Settings
- BatchRestore__AsyncThreshold=50
- BatchRestore__MaxSyncEmails=150
- BatchRestore__MaxAsyncEmails=50000
- BatchRestore__SessionTimeoutMinutes=30
- BatchRestore__DefaultBatchSize=50
# Tenant Management Settings
- TenantManagement__MaxSelectedMailboxes=1000
# BatchOperation Settings
- BatchOperation__BatchSize=50
- BatchOperation__PauseBetweenEmailsMs=50
- BatchOperation__PauseBetweenBatchesMs=250
# Bandwidth Tracking Settings (for IMAP rate limit handling)
- BandwidthTracking__Enabled=false
- BandwidthTracking__DailyLimitMb=25000
- BandwidthTracking__WarningThresholdPercent=80
- BandwidthTracking__PauseHoursOnLimit=24
- BandwidthTracking__TrackUploadBytes=false
# Selection Settings
- Selection__MaxSelectableEmails=250
# View Settings (Privacy & Display)
- View__DefaultToPlainText=true
- View__BlockExternalResources=false
# Dashboard Settings (statistics cache, counter parts, chart periods)
- Dashboard__CacheSeconds=60
- Dashboard__RefreshIntervalMinutes=15
- Dashboard__ShowDirectionSplits=true
- Dashboard__SelectablePeriods=true
# Jobs Settings (background jobs page)
- Jobs__RefreshSeconds=30
# Npgsql Settings
- Npgsql__CommandTimeout=900
# Upload Settings for MBox and EML files
- Upload__MaxFileSizeGB=10
- Upload__KeepAliveTimeoutHours=4
- Upload__RequestHeadersTimeoutHours=2
# Local Import Settings (for CLI imports from mounted volumes)
- LocalImport__AllowedPaths__0=/data/import
# CSV Import Settings (for bulk IMAP account import)
- CsvImport__MaxRows=5000
- CsvImport__MaxFileSizeBytes=10000000
# Deletion Policy Settings (Optional - controls whether email deletion is allowed)
- DeletionPolicy__DeletionAllowed=true
# TimeZone Settings
- TimeZone__DisplayTimeZoneId=Etc/UCT
# Database Maintenance Settings (Optional)
- DatabaseMaintenance__Enabled=false
- DatabaseMaintenance__DailyExecutionTime=02:00
- DatabaseMaintenance__TimeoutMinutes=30
# Attachment Deduplication Settings (Optional - feature is always on)
- AttachmentDeduplication__BatchSize=200
- AttachmentDeduplication__DelayBetweenBatchesMs=0
- AttachmentDeduplication__StartupDelaySeconds=20
- AttachmentDeduplication__OrphanCleanupIntervalHours=12
- AttachmentDeduplication__CommandTimeoutSeconds=300
# Account Storage Settings (Optional - per-account storage display)
- AccountStorage__Enabled=true
- AccountStorage__DailyExecutionTime=02:30
- AccountStorage__BackfillDelayMs=5000
- AccountStorage__RefreshBatchDelayMs=1000
- AccountStorage__CommandTimeoutSeconds=300
# ReleaseNotes Settings (Version Update Splash Screen)
- ReleaseNotes__Enabled=true
# Logging Settings (Optional - defaults to Information level)
- Logging__LogLevel__Default=Information
- Logging__LogLevel__Microsoft_AspNetCore=Warning
- Logging__LogLevel__Microsoft_EntityFrameworkCore_Database_Command=Warning
# Security Settings
- AllowedHosts=mailarchiver.example.com;www.mailarchiver.example.com
# OIDC Configuration (see OIDC_Implementation.md for detailed setup)
- OAuth__Enabled=true
- OAuth__Authority=https://example.com
- OAuth__ClientId=YOUR-CLIENT-ID
- OAuth__ClientSecret=YOUR-CLIENT-SECRET
- OAuth__DisplayName=PocketID SSO
- OAuth__ClientScopes__0=openid
- OAuth__ClientScopes__1=profile
- OAuth__ClientScopes__2=email
- OAuth__DisablePasswordLogin=false
- OAuth__AutoRedirect=false
- OAuth__AutoApproveUsers=false
- OAuth__AdminEmails__0=admin@example.com
ports:
- "5000:5000"
networks:
- postgres
volumes:
# Uncomment the following line to mount a directory for local file imports
# (also configure LocalImport__AllowedPaths__0=/data/import in environment variables)
# - /path/to/your/mbox/files:/data/import
- ./data-protection-keys:/app/DataProtection-Keys
depends_on:
postgres:
condition: service_healthy
postgres:
image: postgres:17-alpine
restart: always
environment:
POSTGRES_DB: MailArchiver
POSTGRES_USER: mailuser
POSTGRES_PASSWORD: masterkey
volumes:
- ./postgres-data:/var/lib/postgresql/data
networks:
- postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U mailuser -d MailArchiver"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
networks:
postgres:
-
Edit the database configuration in the
docker-compose.ymland set a secure password in thePOSTGRES_PASSWORDvariable and theConnectionString. -
If you want to use authentication (which is strongly recommended), define a
Authentication__UsernameandAuthentication__Passwordwhich is used for the admin user. -
Adjust the
TimeZone__DisplayTimeZoneIdenvironment variable to match your preferred timezone (default is "Etc/UCT"). You can use any IANA timezone identifier (e.g., "Europe/Berlin", "Asia/Tokyo"). -
Optionally configure the
Logging__LogLevelenvironment variables to control the verbosity of application logs. See the Logging Settings section below for available options. -
Configure a reverse proxy of your choice with https and authentication to secure access to the application.
⚠️ Attention: The application itself does not provide encrypted access via https! It must be set up via a reverse proxy!
-
Initial start of the containers:
-
Restart containers:
-
Access the application
-
Login with your defined credentials and add your first email account:
- Navigate to "Email Accounts" section
- Click "New Account"
- Enter your server details and credentials
- Save and start archiving!
- If you want, create other users and assign accounts.
📚 Environment Variable Explanations¶
🗄️ Database Connection¶
ConnectionStrings__DefaultConnection: The connection string to the PostgreSQL database. Modify theHost,Database,Username, andPasswordvalues as needed.
🔐 Authentication Settings¶
Authentication__Username: The username for the admin account.Authentication__Password: The password for the admin account.Authentication__SessionTimeoutMinutes: The session timeout in minutes.Authentication__CookieName: The name of the authentication cookie.Authentication__CookieSameSite: Configures the SameSite attribute for authentication, session, and CSRF protection cookies. Valid values are:Strict(default): Maximum security. Cookies are only sent with same-site requests. This may cause issues when navigating to the application from external links (e.g., clicking a link from another website), as the existing session won't be recognized.Lax: Recommended when using a reverse proxy. Cookies are sent with top-level navigations and same-site requests, allowing users to follow external links to the application while maintaining CSRF protection for POST requests.None: Cookies are sent with all requests. Requires HTTPS and theSecureattribute. Only use this if you have specific cross-site requirements and understand the security implications.
🔌 REST API Settings¶
The optional read-only REST API is disabled by default. See the REST API guide for the full reference.
- Api__Enabled: Master switch for the read-only REST API (default false). When false, all /api/* routes return 404.
- Api__AllowAttachmentDownloads: Allow downloading attachment bytes through the API (default true). When false, the attachment endpoint returns 403.
- Api__EnableSwaggerUi: Expose Swagger UI at /apidocs and the OpenAPI document at /apidocs/spec/v1.json (default true). Both require a logged-in browser session.
- Api__DefaultPageSize: Default page size for list endpoints when not specified (default 20).
- Api__MaxPageSize: Maximum allowed page size; larger requests are clamped (default 100).
- Api__RateLimitPerMinute: Fixed-window request budget per API key per minute (default 120).
🤖 MCP Server Settings¶
The optional MCP (Model Context Protocol) server exposes the same read-only mail access as the REST API to AI agents over the Streamable HTTP transport. It is disabled by default. See the MCP guide for the full reference. MCP reuses the exact same API keys as the REST API (ma_... bearer tokens) and the same per-user account-access scoping.
- Mcp__Enabled: Master switch for the MCP server (default false). When false, the /mcp endpoint returns 404.
- Mcp__AllowAttachmentDownloads: Allow the get_attachment tool to return attachment bytes (default true). When false, the tool returns an error.
- Mcp__DefaultPageSize: Default page size used by search_emails when the caller omits pageSize (default 20).
- Mcp__MaxResults: Upper bound for the page size of search_emails; larger values are clamped (default 100).
- Mcp__MaxAttachmentBytes: Maximum attachment size (in bytes) the get_attachment tool will return inline as base64; larger attachments are refused (default 10000000 ≈ 10 MB).
- Mcp__RateLimitPerMinute: Fixed-window request budget per API key per minute (default 120).
📨 MailSync Settings¶
MailSync__IntervalMinutes: The interval in minutes between email synchronization. This is the global default; each account can override it individually from the Create/Edit page (leave empty to use this default).MailSync__FullSyncIntervalHours: Optional global default for automatic full resyncs, in hours. When unset (the default), no automatic full sync runs unless a per-accountFullSyncIntervalHoursvalue is set on the Create/Edit page. Per-account values override this global default.MailSync__TimeoutMinutes: Per-account sync timeout in minutes. A sync that runs longer stops at the next message boundary, keeps its checkpoints and resumes on the next run; the job is reported asTimed OutandLastSyncis not advanced. Applies to scheduled syncs only, a sync started from the UI (manual sync or full resync) runs without a timeout.0(the default) or any non-positive value means no timeout. Review the value before upgrading if you sync large mailboxes. See Synchronization.md.MailSync__ConnectionTimeoutSeconds: The connection timeout for IMAP connections in seconds.MailSync__CommandTimeoutSeconds: The command timeout for IMAP commands in seconds.MailSync__AlwaysForceFullSync: Whether to always force a full sync (true/false).MailSync__IgnoreSelfSignedCert: Whether to ignore self-signed certificates (true/false).MailSync__MaxConcurrentSyncs: How many account syncs may run at the same time. Default1(sequential, backwards-compatible). The scheduler refills slots as they come free rather than waiting for a whole batch, so one slow mailbox no longer holds up the others. Increase to sync multiple accounts concurrently — keep in mind provider rate limits and local resource usage. See Synchronization.md.MailSync__InterAccountDelaySeconds: Optional stagger delay in seconds applied at the end of each account sync task. Default0(no delay). Useful to avoid burst-starts whenMaxConcurrentSyncs > 1.MailSync__MaxIssuesPerKind: How many problems of each kind a sync job remembers for the account page. Failed folders, missing folders and failed messages are budgeted separately, so a flood of one kind cannot push the others out of view; anything beyond the budget is counted rather than kept. Default20.0switches the detail off and leaves only the counters. See Synchronization.md.MailSync__GlobalExcludedFolders__<n>: Folders excluded from synchronization for every account, on top of each account's own excluded-folders list. Empty by default, so existing setups are unaffected. The two lists are additive — a folder is skipped when it matches either — and both use the same matching rules: exact match on the full path, exact match on the folder name, a path-suffix match (soDraftsalso matchesINBOX/DraftsandINBOX.Drafts), and everything below the folder an entry names whenMailSync__ExcludeSubfoldersis switched on. Matching is case-insensitive. Useful when importing many mailboxes from the same server, where the alternative is maintaining an identical exclusion list on every account. No folder is excluded by default; which names are worth listing depends on the server and its language. Example for a mailbox tree that also carries calendar and contact folders:- MailSync__GlobalExcludedFolders__0=Calendar - MailSync__GlobalExcludedFolders__1=Calendars - MailSync__GlobalExcludedFolders__2=Contacts - MailSync__GlobalExcludedFolders__3=AddressBook - MailSync__GlobalExcludedFolders__4=Tasks - MailSync__GlobalExcludedFolders__5=TaskList - MailSync__GlobalExcludedFolders__6=Notes - MailSync__GlobalExcludedFolders__7=NoteList - MailSync__GlobalExcludedFolders__8=JournalMailSync__ExcludeSubfolders: Whether an exclusion entry also covers the folders below the one it names. Applies to the per-account list and the installation-wide one alike, and to both providers. Defaultfalse, matching the behaviour before the option existed: an entryDeleted Itemsmatches only that folder, and a tree is kept out of the archive by naming its root and every folder below it. Set totrueto have an entry take the tree underneath as well — an entryDeleted Itemsthen also keepsDeleted Items/2024and the rest of that branch out of the archive. The rule anchors on the path separator, so it takes whole folders and never part of a name:ArchivecoversArchiveandArchive/Team, and leaves a mail folder calledTeam Archive Reviewalone. A.counts as a separator too, the same way the path-suffix rule treats it.
📤 BatchRestore Settings¶
BatchRestore__AsyncThreshold: The number of emails that triggers async processing.BatchRestore__MaxSyncEmails: The maximum number of emails for sync processing.BatchRestore__MaxAsyncEmails: The maximum number of emails for async processing.BatchRestore__SessionTimeoutMinutes: The session timeout for batch restore in minutes.BatchRestore__DefaultBatchSize: The default batch size for email operations.
🏢 Tenant Management Settings¶
TenantManagement__MaxSelectedMailboxes: Maximum number of mailboxes that can be added in a single Tenant Management operation. Default is1000. Increase this for very large tenants, or lower it to prevent accidental mass imports. When the limit is exceeded, the operation is rejected with a validation error and no accounts are created. See M365 Tenant Import Guide for details.
📦 BatchOperation Settings¶
BatchOperation__BatchSize: The batch size for email operations.BatchOperation__PauseBetweenEmailsMs: The pause between individual emails in milliseconds.BatchOperation__PauseBetweenBatchesMs: The pause between batches in milliseconds.
📤 Offload Settings¶
Settings for the date-windowed offload. All defaults reproduce the behaviour the application had before the feature existed, so an installation that does not configure this section is unaffected.
- Offload__MaxConcurrentJobs: How many restore or offload jobs may run at the same time. Default is 1, which keeps the strictly serial processing the job queue has always used. At most one job per target mailbox runs regardless of this value.
- Offload__PrefetchMaxMessages: Upper bound on how many messages are indexed from a target mailbox for duplicate detection. Default is 500000. Above it the check narrows to a single folder and logs that the scope was reduced.
- Offload__ExcludedSourceFolders__0, __1, ...: Source folders that are never offloaded, matched before renaming and covering subfolders. Empty by default.
- Offload__FolderRenameMap__<SourceFolder>: Rewrites the leading segments of a source folder path, for example Offload__FolderRenameMap__Sent Items=Sent. Empty by default.
- Offload__MarkAsSeen: Whether appended mail is flagged as read. Default is true, matching the existing restore behaviour.
Both folder settings ship empty on purpose: rewriting or dropping folders without being asked would surprise anyone already using the restore path. A run with no configuration therefore migrates everything, spam folders included, and creates a second set of special folders next to the target's own.
📊 Bandwidth Tracking Settings¶
BandwidthTracking__Enabled: Enable or disable bandwidth tracking for IMAP rate limit handling (true/false). Default isfalse. When enabled, the system tracks bandwidth usage per account and can pause synchronization when provider limits are reached. See Rate Limit Handling for detailed information.BandwidthTracking__DailyLimitMb: Daily download limit in megabytes per account. Default is25000(25 GB). For providers with bandwidth limits, set this to match their rate limit (e.g.,2500for providers with ~2500 MB daily limits). The system will pause syncing when this limit is reached.BandwidthTracking__WarningThresholdPercent: Percentage of the daily limit at which warning messages are logged. Default is80. When bandwidth usage reaches this percentage, warnings are logged to help monitor approaching limits.BandwidthTracking__PauseHoursOnLimit: Number of hours to pause synchronization when the daily limit is reached. Default is24. After this period, the limit flag is automatically cleared and syncing resumes.BandwidthTracking__TrackUploadBytes: Whether to also track upload bandwidth (true/false). Default isfalse. Most IMAP providers only limit downloads, so this is typically not needed.
🎯 Selection Settings¶
Selection__MaxSelectableEmails: The maximum number of emails that can be selected at once.
👁️ View Settings (Privacy & Display)¶
View__DefaultToPlainText: Controls the default email view mode for privacy and tracking prevention (true/false). Default isfalse.- When set to
true: Emails open in plain-text view by default, preventing automatic loading of tracking pixels, external images, and web beacons. This is recommended for users concerned about email tracking and privacy. - When set to
false: Emails open in HTML view by default, displaying the formatted email with images and styling. - Users can toggle between HTML and plain-text views using a button in the email details page (only visible when both formats are available).
-
The "Full View" link respects the currently selected view mode.
-
View__BlockExternalResources: Blocks external resources (remote images, external CSS, external scripts, web fonts, etc.) in HTML email views to prevent tracking and improve privacy (true/false). Default isfalse. - When set to
true: External resources are filtered out when displaying HTML emails. Only inline content and data URIs (including inline attachments viacid:references) are displayed. - When set to
false: HTML emails are displayed with all their original external resources. - Important: This setting only affects email display. Archived emails are stored completely unchanged in the database with all original content preserved.
- Blocked resources include:
- Remote images (tracking pixels, external images hosted on servers)
- External CSS stylesheets
- External fonts via @font-face
- External CSS imports via @import
- External background images
- Allowed resources:
- Inline images embedded as data: URIs
- Inline attachments referenced via cid: URIs
- Inline CSS styles and style tags
- This setting works independently from
DefaultToPlainTextand provides an additional layer of privacy protection when viewing HTML emails.
📊 Dashboard Settings¶
Dashboard__ShowDirectionSplits: Whether the counter cards carry their incoming and outgoing parts, and the account card the number of distinct domains. Default istrue. Set tofalseand those numbers are not computed rather than computed and hidden: the counters read a plain count, and the attachment count stops joining to the mail it hangs on, which is the one query these parts add.Dashboard__SelectablePeriods: Whether the dashboard charts offer a resolution, a period and arrows to move it. Default istrue. Set tofalseand the charts show the last twelve months by month and the senders of the whole archive, the chart endpoint answers as if it did not exist, and the oldest send date is not looked up.Dashboard__CacheSeconds: How long computed dashboard statistics (totals with their incoming and outgoing splits, per-account counts, the chart series, top senders, recent emails, database size) are kept in the server's in-memory cache. Default is60seconds. Set to0to disable caching and always recompute the statistics. Each chart selection of resolution and period is cached on its own, so a dashboard that is switched between selections holds one entry per selection. Higher values reduce database load in large environments at the cost of more stale numbers. Sync status badges, storage values and the recent-emails list are always fetched live and are not affected by this cache.Dashboard__RefreshIntervalMinutes: How often theDashboardStatsRefreshServicebackground service recomputes the expensive dashboard aggregates (total counts with their incoming and outgoing splits, per-account counts, the number of account domains, the default chart series with its top senders, database size) into theDashboardStatsCachedatabase table. On installations with several million mails the dashboard reads these prepared values instead of aggregating the whole archive in the request path, which turns a ~10 second first load into milliseconds. The chart endpoint answers the default selection (months over one year, incoming senders, the current period) from the same row, so returning to it shows the numbers of the first paint; every other selection is computed on request and kept in the in-memory cache above. Default is15minutes. Set to0to disable the background refresh; the dashboard then computes the statistics live again (with the in-memory cache above still applying). The values shown may lag up to the configured interval behind the actual archive state. A row computed with otherShowDirectionSplitsorSelectablePeriodsvalues than the current ones is not used; the dashboard computes live until the next refresh.
🔄 Jobs Settings¶
Jobs__RefreshSeconds: How often the background jobs page reloads itself while a browser tab has it open. Default is30seconds. Set to0to turn the automatic reload off and refresh by hand. Each reload rebuilds the page from all eight job sources, so on installations with many accounts a longer interval keeps the load down, multiplied by every tab that is open on the page.
🗃️ Npgsql Settings¶
Npgsql__CommandTimeout: The timeout for database commands in seconds.
📥 Upload Settings¶
Upload__MaxFileSizeGB: The maximum file size for uploads in GB.Upload__KeepAliveTimeoutHours: The keep alive timeout for uploads in hours.Upload__RequestHeadersTimeoutHours: The timeout for request headers in hours.
📂 Local Import Settings¶
LocalImport__AllowedPaths__0,LocalImport__AllowedPaths__1, etc.: Whitelist of local directories that the CLI import commands (--import-mbox,--import-eml) are allowed to read files from. Each entry is a path inside the container. You must mount your import files into one of these directories using Docker volumes. Default:/data/import(automatically set to/app/uploadsas fallback).- When using
docker execto run the import command, the file path provided via--filemust be within one of these allowed paths. - This security measure prevents arbitrary file system access from CLI commands.
- Multiple paths can be configured for different import sources.
- See CLI Local Import Guide for detailed usage instructions.
📄 CSV Import Settings¶
CsvImport__MaxRows: Maximum number of CSV rows (mailboxes) processed in a single bulk import. Default is5000. Increase this value for large deployments; lower it to limit the impact of a single import run on database load.CsvImport__MaxFileSizeBytes: Maximum allowed size (in bytes) of the uploaded CSV file. Default is10000000(10 MB). Adjust this value to match your upload limits if needed.- See Account Import Guide for detailed usage instructions on bulk IMAP account import via CSV.
📤 Audit Export Settings¶
Settings for the audit data export page (admin only, reachable from the Logs page). The export generates a ZIP package with tabular mass data (INDEX.XML + CSV tables + DTD) from the existing archive for external audit tools. See the Audit Data Export Guide for usage details.
- AuditExport__DataSupplierName: Default value for the "Data supplier name" form field that identifies your organization in the exported index file. Default: empty.
- AuditExport__DataSupplierLocation: Default value for the "Data supplier location" form field (e.g. company seat). Default: empty.
- AuditExport__Comment: Default value for the "Comment" form field, included as free text in the exported index file. Default: empty.
- AuditExport__OutputDirectory: Directory where the generated ZIP files are stored. Relative paths are resolved against the app content root. Default: exports/audit.
- AuditExport__RetentionDays: Number of days after which completed export files are deleted by the daily cleanup. Default: 30.
- AuditExport__MaxRangeYears: Maximum allowed span between the export period's start and end date. Default: 10.
- The form fields are pre-filled from these defaults but can be edited per export.
- Every export writes a start entry and a result entry to the access log (type "Audit Data Export"), so the history is revision-safe without any DB schema change.
🔒 Deletion Policy Settings¶
DeletionPolicy__DeletionAllowed: Controls whether manual deletion of archived emails is allowed (true/false). Default istrue. When set tofalse:- All archived emails are locked (
IsLocked = true) on startup via the database compliance trigger, preventing any modification or deletion at the database level. - Manual deletion (single and bulk) is blocked on the application level with an error message.
- The column default is adjusted so that newly imported emails are also locked.
- The current policy state is logged to the AccessLogs table on every startup (visible on the Logs page as "Deletion Policy" entries) for auditability.
- Local retention deletion is exempt: emails that fall under a configured retention period are still deleted (they are unlocked immediately before deletion within the retention process).
- Immutability protection: When
IsLocked = true, the database compliance trigger (prevent_locked_email_changes) blocks ANY modification to the email row — all columns are protected, not just a fixed field list. The only exempt columns areIsLockeditself (so that unlocking for retention deletion and startup policy application remains possible) andFolderName(so that IMAP sync can update the folder when an email is moved server-side). The protection is column-agnostic (JSONB-based comparison) and automatically covers future schema additions.
🕐 TimeZone Settings¶
TimeZone__DisplayTimeZoneId: The time zone used for displaying email timestamps in the UI. Uses IANA time zone identifiers (e.g., "Europe/Berlin", "Asia/Tokyo"). Default is "Etc/UCT" for backward compatibility. When importing emails timestamps will be converted to this time zone for display purposes.
🎉 ReleaseNotes Settings (Version Update Splash Screen)¶
ReleaseNotes__Enabled: Enable or disable the version update splash screen (true/false). Default istrue. When enabled, administrators will see a one-time changelog modal after an application update, showing the release notes fetched from GitHub Releases for the current version. Each administrator can dismiss the modal, and it will only reappear for a new version. Set tofalseto completely disable this feature.
🔧 Database Maintenance Settings¶
DatabaseMaintenance__Enabled: Enable or disable automatic daily database maintenance (true/false). Default isfalse. When enabled, the system will automatically run VACUUM ANALYZE operations to optimize database performance and prevent bloat. See Database Maintenance Guide for more details.DatabaseMaintenance__DailyExecutionTime: The time of day when database maintenance should run, in 24-hour format (HH:mm). Default is02:00. Choose a time during low system activity.DatabaseMaintenance__TimeoutMinutes: Maximum time allowed for maintenance operations in minutes. Default is30. Increase this value for larger databases.
🧬 Attachment Deduplication Settings¶
Attachment deduplication stores every unique attachment payload only once (content-addressed by SHA-256) and is a core feature that is always enabled – there is intentionally no on/off switch. Only the batch/scheduling parameters below can be tuned. See the Attachment Deduplication Guide for full details.
AttachmentDeduplication__BatchSize: Number of existing attachments migrated per transaction during the one-time background migration of pre-existing data. Default is200. Larger values migrate faster but use more memory/DB load per batch.AttachmentDeduplication__DelayBetweenBatchesMs: Optional pause (in milliseconds) between migration batches to throttle database load on busy systems. Default is0(no pause).AttachmentDeduplication__StartupDelaySeconds: Delay (in seconds) after application start before the background migration begins, giving the schema migration time to complete. Default is20.AttachmentDeduplication__OrphanCleanupIntervalHours: Interval (in hours) of the always-on garbage collection that removes attachment payloads no longer referenced by any email. Default is12. This runs independently ofDatabaseMaintenance__Enabled.AttachmentDeduplication__CommandTimeoutSeconds: Database command timeout (in seconds) for the migration batch operations (INSERT with SHA-256 hashing and UPDATE). Default is300(5 minutes). Increase this value for very large databases or attachments, or lower it if you want faster failure detection. If a batch still times out, the service automatically retries with half the batch size.
💾 Account Storage Settings¶
The per-account storage display shows the database storage usage (all mail fields + attachments) for each account in the Dashboard "Account Overview" table and the MailAccounts "Show All" table. An autark background service (AccountStorageRefreshService) computes the values by adding up PostgreSQL pg_column_size per column (covering all fields of a mail without reading the mail bodies) and caches them in the AccountStorageCache table. This service runs independently of DatabaseMaintenance__Enabled.
AccountStorage__Enabled: Enable or disable the storage refresh service (true/false). Default istrue. When enabled, the service performs a resumable backfill of pending accounts on startup (crash-safe viaAccountStorageBackfillState) and a daily full refresh thereafter. When disabled, storage values are only updated when emails are synced, imported, or deleted, but the displayed values may become stale over time.AccountStorage__DailyExecutionTime: Time of day (24-hour formatHH:mm) for the daily full refresh of all accounts. Default is02:30. Choose a time during low system activity.AccountStorage__BackfillDelayMs: Delay (in milliseconds) between accounts during the initial backfill on startup. Default is5000. Lower values speed up the backfill but increase database load; raise this value for very large archives to avoid overloading the database.AccountStorage__RefreshBatchDelayMs: Delay (in milliseconds) between accounts during the daily full refresh. Default is1000. Lower values speed up the refresh but increase database load; raise this value for very large archives.AccountStorage__CommandTimeoutSeconds: Database command timeout (in seconds) for the per-account storage calculation (pg_column_sizeover all rows of an account). Default is300(5 minutes). The calculation reads every mail row including TOAST data, which can take well over a minute on very large archives (250k+ emails); the previous 30-second Npgsql default caused the refresh to fail silently and left the dashboard showing stale values. Raise this value if the calculation still times out on your archive.
💡 Note: Storage values are refreshed immediately after each mail sync, import, or retention deletion, so the displayed values stay current even without the daily refresh. The daily refresh is a safety net that catches edge cases (e.g., direct database changes).
📝 Logging Settings¶
Logging__LogLevel__Default: The default log level for the application. Available levels are:Trace,Debug,Information,Warning,Error,Critical,None. Default isInformation.Logging__LogLevel__Microsoft_AspNetCore: Log level for ASP.NET Core framework messages. Default isWarning.Logging__LogLevel__Microsoft_EntityFrameworkCore_Database_Command: Log level for Entity Framework database commands. Default isWarning.
🛡️ Security Settings¶
AllowedHosts: A semicolon-separated list of host names that the application is allowed to serve. This helps prevent HTTP Host header attacks. Example:AllowedHosts=mailarchiver.example.com;www.mailarchiver.example.com. Important: Do not use*in production environments as it disables host header validation.
🔐 Credential Encryption at Rest¶
Mail account credentials (IMAP password, M365 client secret and MSA OAuth tokens) can be encrypted in the database. The encryption key is kept outside the database (environment variable / Docker secret), so a database dump alone is not enough to read the credentials.
Security__CredentialEncryptionKey: Base64 encoded 256-bit (32 byte) key encryption key. Generate one with: Set the value as an environment variable (recommended:Security__CredentialEncryptionKeyin your compose file or container environment). The application does not support_FILEstyle Docker secrets — if you use a Docker secret, mount it and load its content into the environment variable in your entrypoint. Never commit the key toappsettings.json. When a key is configured, credentials are encrypted on write, and a background service encrypts existing plain text credentials once.Security__EncryptAccountCredentials: Enable or disable encryption of new/updated credential values (true/false). Default istrue. When no key is configured, credentials are stored as plain text and a warning is logged at startup — existing installations are not affected until a key is provided.Security__CredentialEncryptionBackfill: Whether the background service should encrypt existing plain text credentials on startup (true/false). Default istrue. No-op without a configured key.Security__CredentialEncryptionKeyPrevious: Optional previous key, only used to decrypt values encrypted with it (for key rotation). New values are always encrypted withSecurity__CredentialEncryptionKey.
⚠️ Key rotation: after rotating, keep
Security__CredentialEncryptionKeyPreviousconfigured until every credential has been rewritten (e.g. by editing the account or an automatic OAuth token refresh) — existing values encrypted with the old key are not automatically re-encrypted with the new key. Once you remove the previous key, any credential still encrypted with it becomes unreadable and has to be entered again. As long as it is configured, it must be kept as safe as the current key.⚠️ Keep the key safe: Without the key, encrypted credentials cannot be recovered and have to be entered again. Back it up together with (but separately from) your database backups.
💡 Existing installations: Upgrade safely — as long as no
Security__CredentialEncryptionKeyis set, nothing changes. Set the key when you are ready; existing plain text credentials remain readable and are encrypted in the background.
🔐 OIDC Configuration¶
For detailed setup instructions for OpenID Connect authentication, see OIDC Implementation Guide.
Basic OIDC Settings¶
OAuth__Enabled: Enable or disable OIDC authentication (true/false)OAuth__Authority: The OpenID Connect authority URL (e.g., https://sts.windows.net/{TENANT-ID}/ for Azure AD)OAuth__ClientId: The client ID assigned by your identity providerOAuth__ClientSecret: The client secret assigned by your identity providerOAuth__DisplayName: Optional display name shown on the OIDC login button and auto-redirect page (e.g.,PocketID SSO). If omitted, the generic "Login with OAuth" label is used.OAuth__ClientScopes__0: First scope requested from the identity provider (openid)OAuth__ClientScopes__1: Second scope requested from the identity provider (profile)OAuth__ClientScopes__2: Third scope requested from the identity provider (email)OAuth__AutoApproveUsers: Automatically approve new OIDC users without requiring manual admin approval (true/false). Default isfalse.
User Provisioning Settings¶
OAuth__AutoApproveUsers: Automatically approve new OIDC users without requiring manual admin approval (true/false). Default isfalse. When enabled, users who authenticate via the OIDC provider are immediately activated and can access the application. When disabled (default), new OIDC users are created as inactive and require manual activation by an administrator. See Auto-Approve OIDC Users for detailed information.OAuth__AdminEmails__0,OAuth__AdminEmails__1, etc.: Email addresses that should be automatically provisioned as administrators. Users with these email addresses will be created as active admins on first OAuth login, bypassing the normal approval process. Email matching is case-insensitive.
Passwordless Login Settings¶
OAuth__DisablePasswordLogin: Hide username/password fields on login page (true/false). Default isfalse. When enabled, only the OAuth login button is displayed.OAuth__AutoRedirect: Automatically redirect users to OAuth provider (true/false). Default isfalse. RequiresOAuth__DisablePasswordLoginto betrue. Users will see a brief loading screen before being redirected.
Example: Full OIDC-First Configuration¶
environment:
- OAuth__Enabled=true
- OAuth__Authority=https://login.microsoftonline.com/YOUR_TENANT_ID/v2.0
- OAuth__ClientId=your-client-id
- OAuth__ClientSecret=your-client-secret
- OAuth__DisplayName=PocketID SSO
- OAuth__ClientScopes__0=openid
- OAuth__ClientScopes__1=profile
- OAuth__ClientScopes__2=email
- OAuth__DisablePasswordLogin=true
- OAuth__AutoRedirect=true
- OAuth__AutoApproveUsers=true
- OAuth__AdminEmails__0=admin@example.com
- OAuth__AdminEmails__1=manager@example.com
🔐 Kestrel HTTPS Configuration (Optional)¶
While the application is meant to be accessed through a reverse proxy with HTTPS, you can also configure the Kestrel web server to use SSL/TLS certificates. This provides end-to-end encryption between the reverse proxy and the application container.
Configuration Steps¶
-
Generate or obtain an SSL certificate in PFX format (e.g.,
localhost.pfx) -
Add the following environment variables to your
docker-compose.ymlfor themailarchive-appservice:
environment:
# Kestrel HTTPS Settings
- Kestrel__Endpoints__Http__Url=http://0.0.0.0:5000
- Kestrel__Endpoints__Https__Url=https://0.0.0.0:5001
- Kestrel__Endpoints__Https__Certificate__Path=/https/localhost.pfx
- Kestrel__Endpoints__Https__Certificate__Password=MyPassword
- Update the ports mapping in the
mailarchive-appservice:
- Add a volume mapping for the certificate:
- Place your certificate file (e.g.,
localhost.pfx) in the./certsdirectory on your host system.
Environment Variable Explanations¶
Kestrel__Endpoints__Http__Url: HTTP endpoint URL (default: http://0.0.0.0:5000)Kestrel__Endpoints__Https__Url: HTTPS endpoint URL (default: https://0.0.0.0:5001)Kestrel__Endpoints__Https__Certificate__Path: Path to the PFX certificate file inside the containerKestrel__Endpoints__Https__Certificate__Password: Password for the PFX certificate file
💡 Note: This configuration is optional. If you're using a reverse proxy with HTTPS (recommended), the communication between reverse proxy and application can remain HTTP. However, for maximum security in sensitive environments, you may want to enable HTTPS on Kestrel as well to encrypt the entire communication path.
🔑 Secrets Management for Production¶
Hardcoding sensitive data such as database passwords, admin credentials, and OAuth client secrets directly in docker-compose.yml is a security risk, especially when the file is checked into version control. The recommended approach is to externalize these values into a .env file that Docker Compose loads automatically.
📄 The .env File¶
Create a .env file in the same directory as your docker-compose.yml with the following content:
# Database
POSTGRES_PASSWORD=YourSecureDBPassword123!
# Admin Account
AUTH_USERNAME=admin
AUTH_PASSWORD=YourSecureAdminPassword456!
# OIDC / OAuth Secrets
OAUTH_CLIENT_SECRET=YourOAuthClientSecret
# Credential Encryption at Rest (generate with: openssl rand -base64 32)
CREDENTIAL_ENCRYPTION_KEY=YourBase64Encoded32ByteKey
# Kestrel HTTPS (optional)
KESTREL_CERT_PASSWORD=YourCertPassword
Important: Docker Compose automatically reads the .env file from the same directory — you do not need to reference it manually. All variables defined in .env are available in docker-compose.yml via the ${VARIABLE_NAME} syntax.
🐳 Adapted docker-compose.yml¶
The docker-compose.yml from the installation steps should be adapted to use placeholders instead of hardcoded secrets:
services:
mailarchive-app:
image: s1t5/mailarchiver:latest
restart: always
environment:
# Database Connection
- ConnectionStrings__DefaultConnection=Host=postgres;Database=MailArchiver;Username=mailuser;Password=${POSTGRES_PASSWORD}
# Authentication Settings
- Authentication__Username=${AUTH_USERNAME}
- Authentication__Password=${AUTH_PASSWORD}
# ... other settings remain unchanged ...
# OIDC Configuration
- OAuth__ClientSecret=${OAUTH_CLIENT_SECRET}
# Credential Encryption at Rest
- Security__CredentialEncryptionKey=${CREDENTIAL_ENCRYPTION_KEY}
# ... ports, volumes, networks ...
postgres:
image: postgres:17-alpine
restart: always
environment:
POSTGRES_DB: MailArchiver
POSTGRES_USER: mailuser
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
# ... volumes, networks ...
📋 .env.example Template for Administrators¶
Create a .env.example file in your repository that serves as a template for other administrators. It contains placeholder values and documentation but no real secrets:
# ============================================
# Mail Archiver - Environment Configuration
# ============================================
# Copy this file to .env and fill in your values.
# Never commit the actual .env file to version control!
# --- PostgreSQL ---
POSTGRES_PASSWORD=change_me_db_password
# --- Admin Account ---
AUTH_USERNAME=admin
AUTH_PASSWORD=change_me_admin_password
# --- OIDC / OAuth ---
OAUTH_CLIENT_SECRET=change_me_client_secret
# --- Credential Encryption at Rest ---
# Generate with: openssl rand -base64 32
CREDENTIAL_ENCRYPTION_KEY=change_me_base64_32_byte_key
#...and so on
✅ Best Practices¶
- Use
.env.exampleas a template - Set strict file permissions — Restrict access to the
.envfile: - Use strong passwords — Generate long, random passwords (at least 16 characters) with a mix of letters, numbers, and special characters.
- Limit
.envfile access — Only the user running Docker Compose should have read permissions to the.envfile.
🔒 Security Notes¶
- Use strong passwords and change default credentials. Passwords should be at least 12 characters long and include a mix of uppercase letters, lowercase letters, numbers, and special characters. Avoid using common words or easily guessable information.
- Consider implementing HTTPS with a reverse proxy in production
- Regular backups of the PostgreSQL database are recommended. For detailed backup and restore procedures, see Backup and Restore Guide.