ozpanel sovereign hosting
Get ozpanel

Operator & API reference

Mail

Stack, routing and relays, forwarding, import and sync, spam, deliverability, mailboxes, client servicing.

Stack

Same mail stack on every node. Domains, mailboxes, aliases, routes are replicated rows; Postfix and Dovecot read them live. No reloads.

PartRole
PostfixSMTP, submission, queue; live MySQL maps; SNI map
DovecotIMAP, POP3, LMTP, Sieve, ManageSieve, SASL
RspamdSpam, DKIM signing, ARC, DMARC, greylisting, rate limits; state in Redis
ClamAVMalware, reject; on by default
ozpanel-mailgwSending limits, external forwards, Sieve outbound, login throttle
PortService
25SMTP in; never rate-deferred
587 / 465Submission STARTTLS / implicit TLS; AUTH after TLS only
143 / 993, 110 / 995IMAP, POP3
4190, 10025, 10041, 11332–11334Loopback: ManageSieve, Sieve reinjection, login policy, Rspamd
  • StorageMaildir ~/mail/<domain>/<localpart> in the tenant home, shared storage. No mount, no mail services; senders retry.
  • ClusterDKIM keys, counters, greylist, Bayes shared. Every node in the MX set by default.
  • AuthHashed password · up to 5 app passwords (user+tag@example.com) · access tokens (XOAUTH2, OAUTHBEARER) · sender must own the From.
  • Login throttlePer client IP: delay after 5, refuse after 20, 10 min window.
  • Suspended tenantNo send, no receive. Mail kept.
Operator · Mail. — ozpanel screenshot
Operator · Mail.

Mail domains

POST /mail/domains {"domain":"example.com"}          # selector, DKIM key, DNS, SNI, webmail opt-in
POST /mail/domains/example.com/mailboxes {"localpart":"user","quota_mb":2048}
Per-domain settingNotesWho
tls_cert_file, tls_key_fileSNI certificate for SMTP/IMAP/POP. POST …/tls/from-caddy wires the web cert.tenant, operator
transportPostfix transport for inbound; empty = localtenant, operator
relayhostDefault smarthost for outboundtenant, operator
rate_hourly, rate_dailySending limit override; ≤ account limit; 0 = plantenant, operator
helo_identityRead-only: the node's name. No per-domain HELO.

GET/PUT /mail/domains/{domain}/settings (PUT merges). Disable: DELETE /mail/domains/{domain}, mail kept.

Tenant · Mail. — ozpanel screenshot
Tenant · Mail.

Routing

Aliases, forwarders, lists, catch-all

POST /mail/domains/example.com/aliases {"source":"sales","destinations":["user@example.com","other@example.net"]}
  • sourceLocal part, full address, or @ for catch-all (explicit only).
  • destinationsSeveral = distribution list. Local mailbox = delivery; other = external forward.
  • LimitPlan mail_aliases, per source.
  • Not availableMailing lists (subscribe, archive, moderation).

External forwarding

Off by default. Needs plan feature external_forwarding (operator) and the tenant switch PUT /mail/account/forwarding {"enabled":true,"default_mailbox_id":N}.

CaseResult
Allowed, under limitRe-sent with signed return path on the tenant's domain; SPF passes; counted
Off, or over limitHeld in the owner's mailbox, owner notified
SpamNot forwarded
Counter downLocal copy now; external retried 4 h
BounceBecomes a notice, never sent to the original sender

Relays

POST /mail/domains/example.com/relays {"name":"smarthost","nexthop":"[smtp.example.net]:587",
  "auth_enabled":true,"username":"user@example.net","password":"…","tls_level":"encrypt"}
FieldValuesDefault
name, nexthoprequired; brackets skip MX lookup
auth_enabled, username, passwordSASL; password AES-256-GCM, never returnedoff
tls_levelnone · may · encrypt · danemay

Platform relays: operator, /mail/relays. Tenant relays: own domains; may also use platform relays.

Routes

MatchWhoCall
Sender mailboxtenant, operatorPOST /mail/domains/{d}/routes {"scope":"mailbox","localpart":"user","relay_id":1}
Sender domaintenant, operator{"scope":"domain","relay_id":1}
Destination domainoperatorPOST /mail/routes {"match_type":"destination_domain","match_value":"example.net","relay_id":1}

Order: mailbox → domain → relayhost → direct. Destination route beats transport. Most specific relay login wins. GET /mail/domains/{d}/routing.

Journaling

BCC to an archive. Domain: PUT /mail/domains/{d}/journal {"direction":"in|out|both","address","label"}. Platform: PUT /mail/journal. One address per scope.

Sending limits

  • AccountPlan email_per_hour, email_per_day; cluster-wide counter.
  • Domainrate_hourly/rate_daily. Per mailbox: not available.
  • CountedExternal recipients only, after acceptance.
  • Over4.7.1 sending limit reached; counter down 4.3.0. Both temporary; sender notified.
  • Outbound limitsCluster-wide; GET /mail/ratelimits.
  • LockedRelay lock (/postfix/relay/lock): submission only, sendmail root-only, PHP mail() off. Tenant outbound 25/465/587 denied. Outbound scan: mark 10, reject 15.
  • Sieve mailPUT /mail/sieve-route {"enabled":true}: redirects and vacation counted, From checked. Off by default.

Import and sync

mbsync from any IMAP. Gmail and Outlook folders handled. Tenant (/mail/migrations) and mailbox owner (/mail/me/migrations).

POST /mail/migrations {"dest_domain":"example.com","dest_localpart":"user",
  "source_host":"imap.example.net","source_port":993,"source_user":"user@example.net",
  "source_password":"…","mode":"pull","interval_minutes":15,"scan_spam":true}
OptionValuesDefault
modeonce · pull (never deletes) · two_way (flags, moves, deletes)
source_port993 TLS, 143 STARTTLS993
interval_minutes5–144015
scan_spamspam to Junk; carried both ways
remote_junkremote spam folderdetected
  • ControlPATCH /mail/migrations/{id} · POST …/action pause | resume | run | cancel · DELETE keeps copied mail.
  • BehaviourCert verified, password encrypted. Backoff to 6 h, pause after 10 failures. 3 per node. Resumes on another node.
  • Moving a domain inEnable mail, create mailboxes (new passwords), aliases, rules, pull syncs, publish new DKIM, switch MX, cancel syncs.
  • Not availablecPanel, Virtualmin or other panel importers.

Mail backup jobs

POST /mail/backups {"name":"nightly","kind":"backup","scope":"domain","target":"example.com",
  "dest_path":"/var/backups/ozpanel/mail","schedule":"0 3 * * *","active":true}
ozpanel mail-backup-run --job 3

Operator. kind backup (maildir copy) | sync (remote). scope all | domain | mailbox. Cron or @daily etc. Status ok · partial · failed.

Spam and malware

ProtectionDefault
ClamAVon, reject
Phishingon
Bayes, autolearnon; per-user statistics a switch
Greylistingon
Inbound rate limitsip 500/h · to_ip 100/h · to 1000/h · bounce_to 50/h
DMARC reportsoff; /rspamd/dmarc
ARCbuilt in
ControlCallWho
Thresholds (greylist < spam < reject, 0–100)PUT /mail/domains/{d}/spam (domain or mailbox) · PUT /mail/me/spamtenant, mailbox owner
skip_greylistsametenant
Allow / block listsPOST …/spam/lists {"list_type":"block","value":"spammer@example.net","mailbox":"user"} · /mail/me/spam/liststenant, mailbox owner
RelaxationsPUT …/spam/relax/{key}: no_reverse_dns, no_message_id, no_date_header, html_onlytenant
Why this scorePOST /rspamd/explaintenant
Engine, platform thresholds, rate limits/rspamd/engine, /rspamd/ratelimit, …/selftestoperator
  • QuarantineSpam held. Release (?learn=ham) or delete (?learn=spam) by tenant (/mail/quarantine) or owner (/mail/me/quarantine).
  • DigestPOST /mail/quarantine/digest/schedule {"hour":7}; send now …/digest.
  • LearningMove in or out of Junk in any client trains (IMAPSieve). /mail/junk-learning.

Deliverability

One-call DNS

POST /mail/domains/{domain}/dns publishes, into the cluster's zone:

NameTypeDefault value
mail.example.comAOne per node (all_nodes) · floating · pinned {"ip":"203.0.113.10"}
example.comMX10 mail.example.com.
example.comTXTv=spf1 a mx ~all (merged)
<selector>._domainkeyTXTDKIM, RSA 2048
_dmarcTXTv=DMARC1; p=none; rua=mailto:postmaster@example.com
autoconfig, autodiscoverCNAMEmail.example.com.
_submission._tcp, _imaps._tcpSRV587, 993

Missing MX, host, SPF, DMARC republished every pass; custom values left alone. Autoconfig/autodiscover XML endpoint: not available (DNS only).

FeatureCallNotes
DKIMGET …/dkim, POST …/dkim/rotateGenerate, publish, rotate
MTA-STS + TLS-RPTPOST …/mta-sts {"mode":"testing|enforce|none","max_age":604800,"report_to"}Policy site and 3 records
DANEPOST …/tlsa3 1 1 on 25, 465, 587, 993; DNSSEC zones only
Server identityGET /mail/identity, POST …/publishHELO A, SPF, PTR check
DMARC reports/rspamd/dmarcSend aggregate reports

Scorecard

GET /mail/domains/{d}/deliverability · /mail/me/deliverability. Grade: good · improvable · action needed.

Checks: MX · SPF (exactly one) · DKIM published and signed · DMARC policy · DNSSEC · MTA-STS · TLS-RPT · TLSA · mail certificate (14 days) · PTR · HELO · Spamhaus ZEN.

Mailboxes and client servicing

ActionCallWho
CreatePOST …/mailboxes localpart, quota_mb, password (generated, shown once)tenant
Quota, suspendPUT …/mailboxes/{lp} quota_mb, statustenant
Reset passwordPOST …/passwordtenant
CompromisedPOST …/secure: new password, app passwords revoked, connections droppedtenant
Connected devicesGET …/devices, POST …/devices/disconnecttenant, owner
App passwordsList, revoke; owner createstenant, owner
Vacation…/autoresponder: enabled, subject, message, start_date, end_datetenant, owner
Filters…/filters: from/to/cc/subject → fileinto, forward, discard, keeptenant, owner
Sign in as this mailboxPOST …/mailboxes/{lp}/impersonate; audited, actions attributedtenant
Quota viewGET /mail/domains/{d}/quota: per mailbox and tenant totaltenant
  • Own passwordMailbox and panel login stay in sync. Reset by email link (/auth/password/reset-request).
  • QuotasPer mailbox (Dovecot, at delivery) and tenant total (mail_quota_mb, at allocation).
  • FiltersOne Sieve script per mailbox, shared by panel, Roundcube, SOGo.
  • Self-diagnosticsGET /mail/me/diagnostics: health, storage, counts, size and send limits.
  • Apple MailProfile per access token: GET /tokens/me/{jti}/apple-mail.
Client settingValue
Servermail.example.com (domain cert via SNI)
IMAP / POP3993 or 143 STARTTLS / 995 or 110 STARTTLS
SMTP465 TLS or 587 STARTTLS, auth required
UserFull address, or the app-password name

Failover

  • InboundAll nodes in the mail host's A set; dead node withdrawn; never emptied. Senders retry.
  • QueuePer node; a stopped node delivers on return.
  • MailboxesShared storage; any node serves them. Dovecot replication not used.

Troubleshooting

QuestionCall
Working?GET /mail/status, /mail/health
Stuck messageGET /mail/queue?domain=&sender=&status= · POST /mail/queue/flush · DELETE /mail/queue/{id}
VolumesGET /mail/stats, /mail/flow, /mail/activity
DriftGET /mail/verify, /mail/postfix/verify, /mail/dovecot/verify, /mailgw/status
SessionsGET /dovecot/sessions
Rebuild data planePOST /mail/reconfigure, /mail/dovecot/reconcile
Postfix/postfix/status, health, limits, raw, maps, POST /postfix/param

CLI: ozpanel mail-backup-run --job N · ozpanel mail-digest · ozpanel mailgw (unit only).

Previous: Apps and containers Next: Webmail, webapp and push