{% if panel_favicon_url %} {% endif %}

{{ panel_title or 'Limristem eMail Admin Panel' }}

Hostname: {{ hostname }} — authenticated session for {{ panel_user }}

{% if notice %}
{{ notice }}
{% endif %} {% if error %}
{{ error }}
{% endif %} {% if page == 'dashboard' %}

Overview

{% for key, value in stats.items() %}
{{ key.replace('_', ' ').title() }} {{ value }}
{% endfor %}

Mail traffic (14 days)

Privacy-safe: only counts and sizes by local mailbox — no subjects or bodies. Open statistics · Traffic list

{% if traffic_chart_svg %}
{{ traffic_chart_svg|safe }}
{% else %}

No traffic chart yet. Stats are collected from Postfix logs every few minutes.

{% endif %} {% if mail_summary and mail_summary.today %}
In today{{ mail_summary.today.inbound }}
Out today{{ mail_summary.today.outbound }}
Avg in / day (30d){{ mail_summary.last_30_days.avg_inbound_per_day }}
Avg out / day (30d){{ mail_summary.last_30_days.avg_outbound_per_day }}
{% endif %}

System Status

{% if system %}
Uptime {{ system.uptime }}
CPU {{ system.cpu.percent }}% Load: {{ system.cpu.load_1|round(2) }}, {{ system.cpu.load_5|round(2) }}
Memory {{ system.ram.percent }}% {{ system.ram.used_str }} / {{ system.ram.total_str }}
Disk {{ system.disk.percent }}% {{ system.disk.used_str }} / {{ system.disk.total_str }}
{% endif %}

Recent queue items

{% if recent_queue %} {% for item in recent_queue %} {% endfor %}
Queue IDSenderRecipients
{{ item.queue_id }} {{ item.sender }} {% for rcpt in item.recipients or [] %}
{{ rcpt.address }}
{% endfor %}
{% else %}

Queue is currently empty.

{% endif %}

Recent backups

{% if recent_backups %} {% for item in recent_backups %} {% endfor %}
NamePath
{{ item.name }} {{ item.path }}
{% else %}

No local backup runs found yet.

{% endif %}
{% elif page == 'traffic' %}

Mail traffic

Only local mailbox, direction, size and time — no subjects, bodies or remote addresses.

Statistics
{% for item in traffic_items %} {% else %} {% endfor %}
WhenDirectionMailboxSize
{{ item.event_at }} {{ 'Received' if item.direction == 'inbound' else 'Sent' }} {{ item.mailbox }} {{ item.size_human }} ({{ item.size_bytes }} B)
No traffic events yet. Wait for the collector timer or open Statistics and click “Collect now”.
{% elif page == 'stats' %}

Statistics

{{ mail_summary.privacy_note if mail_summary else 'Privacy-safe traffic statistics.' }} Retention: {{ mail_summary.retention_days if mail_summary else '—' }} days.

{% if mail_summary %}
Today in / out{{ mail_summary.today.inbound }} / {{ mail_summary.today.outbound }}
7d avg / day in{{ mail_summary.last_7_days.avg_inbound_per_day }}
7d avg / day out{{ mail_summary.last_7_days.avg_outbound_per_day }}
30d avg / day in{{ mail_summary.last_30_days.avg_inbound_per_day }}
30d avg / day out{{ mail_summary.last_30_days.avg_outbound_per_day }}
365d avg / month in{{ mail_summary.last_365_days.avg_inbound_per_month }}
365d avg / month out{{ mail_summary.last_365_days.avg_outbound_per_month }}
{% endif %}

Daily chart ({{ stats_days }} days)

{% if traffic_chart_svg %}{{ traffic_chart_svg|safe }}{% else %}

No data

{% endif %}

By day

{% for row in stats_daily|reverse %} {% else %} {% endfor %}
DayInOutIn bytesOut bytes
{{ row.day }} {{ row.inbound_count }} {{ row.outbound_count }} {{ row.inbound_bytes }} {{ row.outbound_bytes }}
No daily data yet.

By mailbox ({{ stats_days }} days)

{% for row in stats_mailboxes %} {% else %} {% endfor %}
MailboxInOutIn bytesOut bytes
{{ row.mailbox }} {{ row.inbound_count }} {{ row.outbound_count }} {{ row.inbound_bytes }} {{ row.outbound_bytes }}
No per-mailbox data yet.
{% elif page == 'domains' %}

Domains

Add domain
{% if domains %} {% for domain in domains %} {% endfor %}
DomainAccountsAliasesRedirectsStatusOpen
{{ domain.name }} {{ domain.account_count }} {{ domain.alias_count }} {{ domain.redirect_count }} {{ 'active' if domain.is_active else 'inactive' }} Manage
{% else %}

Create your first domain to manage accounts, aliases, redirects and DKIM.

{% endif %}
{% if show_create_form %}

Create domain

Close
{% endif %} {% elif page == 'domain-detail' %}

Back to domains

{{ selected_domain.name }}

Add domain
Max accounts{{ selected_domain.max_accounts_display }}
Used accounts{{ selected_domain.used_accounts }}
Available accounts{{ selected_domain.available_accounts }}
Aliases{{ selected_domain.alias_count }}
Redirects{{ selected_domain.redirect_count }}
Status{{ 'Active' if selected_domain.is_active else 'Inactive' }}
DNS provider{{ selected_domain.dns_provider or 'manual' }}
{% if active_domain_tab == 'settings' %}

Domain settings

DKIM rotation

Active selector: {{ selected_domain.dkim_selector or 'none' }} {% if selected_domain.dkim_rotated_at %} · last rotation {{ selected_domain.dkim_rotated_at }}{% endif %}

{% if selected_domain.dkim_previous_selector %}

Previous selector kept for overlap: {{ selected_domain.dkim_previous_selector }} {% if selected_domain.dkim_previous_expires_at %} until {{ selected_domain.dkim_previous_expires_at }}{% endif %}

{% endif %}

Private key path: {{ selected_domain.dkim_private_path or 'not generated yet' }}

{{ selected_domain.dkim_public_key or 'Generate DKIM to populate the public record.' }}

DNS provider (Cloudflare)

{% if selected_domain.dns_has_api_token %} {% endif %}

Last sync: {{ selected_domain.dns_last_sync_status or 'never' }}

{% elif active_domain_tab == 'accounts' %}

Accounts

Add account
{% if show_create_form %}

Send limits: leave 0 to inherit global service limits.

{% endif %} {% for account in domain_accounts %} {% endfor %}
Email Storage quota Send limits (0=global) Effective send Password Status Suspend / restore Delete
{{ account.email }}

burst / min / hour / day / week / month

{{ account.effective_send_rate_per_hour or 0 }}/h · {{ account.effective_send_rate_per_day or 0 }}/d · {{ account.effective_send_rate_per_month or 0 }}/mo
{{ 'Active' if account.is_active else 'Suspended' }}
{% elif active_domain_tab == 'aliases' %}

Aliases

Add alias
{% if show_create_form %}
{% endif %} {% for alias in domain_aliases %} {% endfor %}
AliasDestinationActiveActions
{{ alias.source }} {{ alias.destination }} {{ 'yes' if alias.is_active else 'no' }}
{% elif active_domain_tab == 'redirects' %}

Redirects

Add redirect
{% if show_create_form %}
{% endif %} {% for redirect in domain_redirects %} {% endfor %}
RedirectTargetActiveActions
{{ redirect.source }} {{ redirect.target_email }} {{ 'yes' if redirect.is_active else 'no' }}
{% else %}

Suggested DNS records

{% for key, value in dns_records.items() %}

{{ key }}

{{ value }}
{% endfor %}
{% endif %}
{% elif page == 'queue' %}

Mail Queue

{% if queue %} {% for item in queue %} {% endfor %}
Queue IDQueueSenderRecipientsReasonActions
{{ item.queue_id }} {{ item.display_state or '—' }} {{ item.sender }} {% for rcpt in item.recipients or [] %}
{{ rcpt.address }}
{% endfor %}
{{ item.display_reason or '—' }} {% for action in ['hold', 'release', 'requeue', 'delete'] %}
{% endfor %}
{% else %}

Queue is empty.

{% endif %}
{% elif page == 'import' %}

Mailbox import

Transfer every folder and message (including Sent) from a remote IMAP server into a local Limristem mailbox. Jobs run in a dedicated queue.

{% if active_import_tab == 'new' %}

Source IMAP credentials

Choose the local destination account, then enter the remote mailbox credentials. Use Test connection before queuing a full import.

{% if not import_accounts %}

Create at least one local account before importing mail.

{% else %}

Passwords are encrypted at rest. Plain IMAP is only for trusted networks; prefer IMAP SSL or STARTTLS.

{% endif %}
{% else %}

Import jobs

Live progress refreshes every 2 seconds while jobs are pending or running.

{% if import_jobs %} {% for job in import_jobs %} {% endfor %}
ID Status Destination Source Progress Transferred Speed ETA Folder Actions
{{ job.id }} {{ job.status }} {{ job.destination_username }} {{ job.source_username }}
{{ job.source_host }}:{{ job.source_port }} ({{ job.source_protocol }})
{{ '%.1f'|format(job.progress_percent) }}% · {{ job.messages_done }}/{{ job.messages_total }} msgs · folders {{ job.folders_done }}/{{ job.folders_total }}
{{ job.bytes_done_human }}
of {{ job.bytes_total_human }} · {{ job.bytes_remaining_human }} left
{{ job.speed_human }} {{ job.eta_human or '—' }} {{ job.current_folder or '—' }} {% if job.status_message %}
{{ job.status_message }}
{% endif %} {% if job.last_error %}
{{ job.last_error }}
{% endif %}
{% if job.status in ['pending', 'running'] %}
{% endif %} {% if job.status not in ['running'] %}
{% endif %}
{% else %}

No import jobs yet. Start one from the New import tab.

{% endif %}
{% endif %}
{% elif page == 'backups' %}

Backups

{% if active_backup_tab == 'schedules' %} {% if action == 'create' or selected_backup_schedule.get('id') %}

{{ 'Edit schedule' if selected_backup_schedule.get('id') else 'Create schedule' }}

Cron-style schedule fields: minute hour day month weekday. Use * to match any value.

Cancel

Limristem eMail uses the database credentials already stored in the main configuration file. A schedule only decides whether the MariaDB dump is included.

{% else %}

Backup schedules

Create new schedule
{% if backup_schedules %} {% for item in backup_schedules %} {% endfor %}
NameScheduleStorageDBRedisCompressionStatusActions
{{ item.name }} {{ item.cron_expression }} {{ item.storage_id or 'Local only' }} {{ item.include_database }} {{ item.include_redis }} {{ item.compression }} {{ item.enabled }} Edit
{% else %} {% endif %}
{% endif %} {% elif active_backup_tab == 'storages' %} {% if action == 'create' or selected_backup_storage.get('id') %}

{{ 'Edit storage' if selected_backup_storage.get('id') else 'Create storage' }}

Cancel

When encryption is enabled, Limristem eMail keeps only the public key for future backup exports and prompts you to download the private key once.

{% else %}

Backup storages

Create new storage
{% if private_key_download_token %}
New encryption key ready.
{% endif %} {% if backup_storages %} {% for item in backup_storages %} {% endfor %}
NameTypeTargetEncryptionActions
{{ item.name }} {{ item.type }} {{ item.bucket or item.host or item.path or 'local' }} {{ item.encrypt }} Edit
{% else %} {% endif %}
{% endif %} {% elif active_backup_tab == 'runs' %}

Available local backup runs

{% if backup_runs %} {% for item in backup_runs %} {% endfor %}
NamePathMetadata
{{ item.name }} {{ item.path }} {{ item.metadata or 'n/a' }}
{% else %}

No local backups found.

{% endif %}
{% else %}

Global backup settings

{% for key in backup_field_order %} {% endfor %}
SettingValue
{{ backup_field_labels.get(key, key) }} {% if key in backup_numeric_fields %} {% elif backup_field_options.get(key) %} {% else %} {% endif %}
{% endif %}
{% elif page == 'security' %}

Admin MFA (panel + API)

TOTP and/or SMS OTP protect the web panel and the management API. SMTP/IMAP/POP cannot do interactive OTP mid-session — for mailboxes use app passwords (API /accounts/{id}/app-passwords) and optionally require_app_password.

TOTP (authenticator app)

Status: {% if mfa_status and mfa_status.totp_enabled %}enabled {% elif mfa_status and mfa_status.totp_pending %}pending confirmation {% else %}disabled{% endif %}

{% if totp_enroll %}

Secret (store offline / scan with authenticator):

{{ totp_enroll.secret }}

URI: {{ totp_enroll.provisioning_uri }}

{% else %}
{% if mfa_status and (mfa_status.totp_enabled or mfa_status.totp_pending) %}
{% endif %} {% endif %}

SMS OTP

Status: {% if mfa_status and mfa_status.sms_enabled %}enabled ({{ mfa_status.sms_phone_masked }}) {% else %}disabled{% endif %}

SMS providers

Headers: X-Client-Id, X-Client-Secret, X-Api-Key (Crilide API style).

{% for provider in mfa_providers or [] %}

{{ provider.label }} ({{ provider.code }})

{% if provider.code == 'sms2sms' %} {% else %} {% endif %}
{% endfor %}

Banned IPs

{% for item in bans %} {% else %} {% endfor %}
CreatedIPReasonSourceServicesAction
{{ item.created_at or '—' }} {{ item.ip }} {{ item.reason }} {{ item.source or 'manual' }} {{ item.services or item.jail or '—' }}
No banned IPs found.

Firewall

{% for rule in firewall_rules %} {% endfor %}
Port / group / rangeProtocolSource IP / subnetStatusActions

Use one row per nftables rule. Ports may be a single port, a group of ports, or ranges. Leave source empty to allow any origin.

{% elif page == 'operations' %}

Operations

Deliverability, outbound IP reputation self-checks, connectivity probes, inbound DNSBL helpers.

Deliverability check

End-to-end connectivity

TCP + TLS probes to SMTP/IMAP/HTTPS (no mailbox password required).

Inbound DNSBL (Rspamd)

{{ rbl_config.warning }}

{% if operations_result %}

Last result

{{ operations_result }}
{% endif %} {% if reputation_history %}

Recent IP reputation checks

{% for row in reputation_history %} {% endfor %}
WhenIPSourceListedDetail
{{ row.checked_at }} {{ row.entity_value }} {{ row.source }} {{ 'yes' if row.listed else 'no' }} {{ row.detail or '' }}
{% endif %}
{% elif page == 'topology' %}

Topology wizard

How this instance is deployed: Standalone, Routed Mesh, or Storage Cluster.

Mode{{ topology.label }} ({{ topology.mode }})
Local server ID{{ topology.local_server_id }}
Internode auth{{ 'configured' if topology.internode_configured else 'not set' }}

Choose deployment mode

{% for mode in topology_modes %} {% endfor %}
Routed Mesh options
Storage Cluster options
{% if internode_token_once %}
Internode token (copy now):
{{ internode_token_once }}
{% endif %}

Registered servers

{% for s in topology_servers %} {% else %} {% endfor %}
IDNameHostnameSMTPRoleLocalActive
{{ s.server_id }} {{ s.display_name }} {{ s.hostname }} {{ s.smtp_host }}:{{ s.smtp_port }} {{ s.role }} {{ 'yes' if s.is_local else 'no' }} {{ 'yes' if s.is_active else 'no' }}
No peers registered (standalone or not yet joined).
{% elif page == 'logs' %}

Logs

{% for log_name in allowed_logs %} {{ log_name }} {% endfor %}
{{ log_content }}
{% elif page == 'settings' %}

Settings

{% if active_settings_tab == 'general' %}

General settings

Stable is the production package feed. Nightly points at the experimental build channel; only use it on non-production nodes.

Max size: 512 KB
{% if settings_info.get('panel_favicon_path') %}
{% endif %}

Version and updates

Hostname{{ settings_info.get('hostname', hostname) }}
Update channel{{ settings_info.get('update_channel', 'stable') }}
Check endpoint{{ settings_info.get('update_check_url') or (settings_info.get('update_status') or {}).get('endpoint', '—') }}
Current version{{ settings_info.get('current_version', 'unknown') }}
Latest version{{ (settings_info.get('update_status') or {}).get('latest_version', 'not checked yet') }}
Update available{{ 'yes' if (settings_info.get('update_status') or {}).get('update_available') else 'no' }}
{% elif active_settings_tab == 'ssl' %}

Shared TLS certificate

{% if ssl_info.get('cert_info') %} {% endif %}
Status{{ ssl_info.get('status', 'unknown') }}
Mode{{ ssl_info.get('mode', 'unknown') }}
Hostname{{ ssl_info.get('hostname', hostname) }}
Certificate path{{ ssl_info.get('cert_path', 'N/A') }}
Key path{{ ssl_info.get('key_path', 'N/A') }}
Subject{{ ssl_info['cert_info'].get('subject', 'N/A') }}
Issuer{{ ssl_info['cert_info'].get('issuer', 'N/A') }}
Valid From{{ ssl_info['cert_info'].get('not_valid_before', 'N/A') }}
Valid To{{ ssl_info['cert_info'].get('not_valid_after', 'N/A') }}
{% if ssl_info.get('certs_match') %}

The same certificate is reused for Postfix, Dovecot, and the HTTPS endpoints served by Nginx.

{% else %}

Notice: Postfix and/or Dovecot are using different certificates!

{% if ssl_info.get('postfix_cert_info') %} {% endif %} {% if ssl_info.get('dovecot_cert_info') %} {% endif %}
Postfix Cert Path{{ ssl_info.get('postfix_cert_path', 'N/A') }}
Postfix Cert Subject{{ ssl_info['postfix_cert_info'].get('subject', 'N/A') }}
Dovecot Cert Path{{ ssl_info.get('dovecot_cert_path', 'N/A') }}
Dovecot Cert Subject{{ ssl_info['dovecot_cert_info'].get('subject', 'N/A') }}
{% endif %}

SSL actions

{% elif active_settings_tab == 'limits' %}

Service limits

{% for key, value in limits.items()|sort %} {% endfor %}
KeyValue
{{ limit_field_labels.get(key, key) }}
{{ key }}
{% else %}

API Credentials

Admin user{{ api_info.get('api_admin_user', 'N/A') }}
Endpoint{{ api_info.get('api_endpoint', 'N/A') }}
Hostname{{ api_info.get('hostname', 'N/A') }}

API Usage

Authenticate using HTTP Basic Auth with your API admin credentials.

Example requests

curl -u <user>:<pass> {{ api_info.get('api_endpoint', '') }}health
curl -u <user>:<pass> {{ api_info.get('api_endpoint', '') }}domains
curl -u <user>:<pass> {{ api_info.get('api_endpoint', '') }}accounts
{% endif %}
{% elif page == 'limits' %}

Service limits & anti-spam

Auth lockouts, max message size, SMTP rates, and per-user authenticated send quotas (Rspamd).

{% for key, value in limits.items()|sort %} {% endfor %}
ParameterValue
{{ limit_field_labels.get(key, key) }}
{{ key }}
{% elif page == 'api' %}

API Credentials

Admin user{{ api_info.get('api_admin_user', 'N/A') }}
API endpoint{{ api_info.get('api_endpoint', 'N/A') }}
Hostname{{ api_info.get('hostname', 'N/A') }}
{% if generated_api_password %}
New API password: {{ generated_api_password }}
Save this password now — it will not be shown again. Only the hash is stored.
{% endif %}

API Usage

Authenticate using HTTP Basic Auth with your API admin credentials.

Example requests

curl -u <user>:<pass> {{ api_info.get('api_endpoint', '') }}health
curl -u <user>:<pass> {{ api_info.get('api_endpoint', '') }}domains
curl -u <user>:<pass> {{ api_info.get('api_endpoint', '') }}accounts

Available endpoints

MethodPathDescription
GET/healthHealth check (public)
GET/health/detailsDetailed health (admin)
GET/POST/domainsList / create domains
GET/POST/accountsList / create accounts
GET/POST/aliasesList / create aliases
GET/POST/redirectsList / create redirects
GET/POST/reputationList / create reputation records
GET/admin/queueMail queue management
GET/admin/bansIP ban management
GET/PATCH/admin/limitsService limits
GET/PATCH/admin/backupsBackup configuration
GET/admin/sslShared TLS certificate status
POST/admin/ssl/selfsignedGenerate a self-signed certificate
POST/admin/ssl/letsencryptRequest a Let's Encrypt certificate
POST/admin/ssl/manualUpload a manual PEM certificate and key
{% endif %}

Accessibility

Text size
Strumenti di Accessibilità