mailcow Toolkit — guide
Before you start
- mailcow-dockerized on Linux (tested with 2026-09 and 2026-09a), installed for example in
/opt/mailcow-dockerized. - Root access on the host and these commands:
dockerwithdocker compose,git,jq,flock,curl,tar,sha256sum, and systemd. - Outgoing HTTPS to medialab.works from the server (license check, trial, Toolkit updates).
- Your license key, from your medialab.works account, or an e-mail address for the free 30-day trial.
- A second host name for webmail (for example
webmail.example.com) in DNS, and a reverse proxy that can serve it. - A folder for backups, for example
/srv/backup/mailcow. - Optional, for DNS & Cloudflare: a Cloudflare account that holds your domains' zones. To find the server's public addresses, the Toolkit asks
ip4.mailcow.emailandip6.mailcow.email(like mailcow's own DNS check).
Installation
Download mailcow-toolkit-<version>.tar.gz from your medialab.works account, unpack it on the mailcow host and run, as root:
sudo ./install.sh --mailcow-dir /opt/mailcow-dockerized \
--webmail-url https://webmail.example.com --backup-dir /srv/backup/mailcow
--mailcow-dir— where mailcow-dockerized is installed (default/opt/mailcow-dockerized).--webmail-url— the webmail address,https://+ host name (an optional port is allowed). Without it the SOGo button keeps mailcow's stock behavior.--backup-dir— where the backups before updates go. Without it, Update now runs without a backup first (the installer warns you).--name— only if you have several mailcow installations on the same host: one name per installation (letters, digits and-).
The installer places the web files in mailcow's data/web (excluded from mailcow's git), the shared folder in data/hooks/phpfpm/.mlw-toolkit, the host agent in /usr/local/sbin/mlw-mailcow-agent-<name>, the settings in /etc/mlw-mailcow-toolkit/<name>.conf, a copy of the installed version in /var/lib/mlw-mailcow-toolkit/<name>/package, and enables five systemd units: one that runs the requests from the admin UI, a timer that checks the license and looks for a new mailcow version when the server starts and once a day, mlw-mailcow-rdns-<name>.timer, which checks the reverse DNS every 6 hours, and mlw-mailcow-domains-<name>.timer, which looks for new mailcow domains every 2 minutes (it does nothing while automatic Cloudflare setup and the new domain wizard are both off), and, from 0.4.0, mlw-mailcow-accounts-<name>.timer, which runs the jobs of Sign-up for mailcow and Cashier for mailcow every 5 minutes. From 0.4.0 the package installs Sign-up and Cashier too (their tables mlw_su_* and mlw_ca_* in mailcow's database); they stay switched off until each has its own license. At the end it reloads php-fpm and checks the license; until the license is active it tells you to open the License section.
Upgrading the Toolkit: from the License section (see below), or by running install.sh again from the new version. Your earlier webmail address and backup folder are kept if you leave them out. Updating to 0.3.0 from the License section also enables the two new timers; the DNS & Cloudflare page then appears in the System menu.
License and free trial
Open System > Updates (Toolkit) (admins only). The License section at the top shows the state of the license and works even without one:
- Enter your license key and press Activate. The key is in your medialab.works account.
- Or start the free 30-day trial: enter your e-mail address, choose monthly or yearly for after the trial and press Start the free trial. Open the Stripe checkout page from the link shown: the card is asked there and not charged during the 30 days. If the address already has a medialab.works account, the link is sent to it by e-mail instead. The page then activates the Toolkit by itself as soon as the trial starts. The trial can be used once per server and per e-mail address.
- From 0.4.0 the page shows one License card per product (License · mailcow Toolkit, License · Sign-up for mailcow, License · Cashier for mailcow), each with its own key and its own trial. A mailcow bundle key is entered in each card. Updates of the package can be started from the card of any product with a valid license. See the Sign-up guide and the Cashier guide.
- With an active license the section shows the key (partly hidden), the type, the end date, the last check, until when the Toolkit works without a connection, and the id of this server. Check now checks right away (at most twice an hour together with updates and rollbacks).
- Remove from this server (after you tick the confirmation) frees the license so you can use it on another mailcow server; the Toolkit stops here.
The license is checked when the server starts and once a day. If medialab.works cannot be reached, the Toolkit keeps working for 7 days after the last successful check.
Without a valid license (none yet, expired, removed, moved to another server, or not checked for more than 7 days) the Toolkit features stop: the SOGo button keeps mailcow's stock behavior, the mailcow update check, e-mails and updates do not run, the DNS checks, the reverse DNS indicator, Cloudflare and the new domain wizard stop, and the page shows only the License section. Nothing is deleted.
Updating or rolling back the Toolkit
With a valid license the License section shows the installed and the latest published version:
- Update the Toolkit to x.y.z downloads the signed archive from medialab.works, checks its size and SHA-256, and queues it for the host agent, which checks it again, runs that version's
install.shand keeps the previous version: if the installation fails, the previous version is installed again. The page shows the progress and reloads when it is done. - Roll back to x.y.z does the same with the previous version allowed by your license.
If your license does not cover the installed version (for example a lifetime license whose update period ended before that version), the Toolkit stops and the License section offers Roll back to the last version your license allows, and the link to renew the update pass.
The webmail host name
SOGo as a mailbox only works on a host name other than the one of the mailcow UI, because the browser keeps one session cookie per host name.
- Point the webmail host name (for example
webmail.example.com) at your server in DNS. - In your reverse proxy, serve it exactly like the mailcow UI: same backend, same headers. It is the same mailcow, only under another name.
- Give it a TLS certificate, like the UI.
Enabling SOGo as a mailbox
- In
mailcow.confsetALLOW_ADMIN_EMAIL_LOGIN=y, then apply it from the mailcow folder withdocker compose up -d. - Make sure the admin has the Login as mailbox user permission.
Then, in E-Mail > Configuration, tab Mailboxes, the SOGo button next to a mailbox opens a new tab on the webmail host, signed in to SOGo as that mailbox. Your admin tab stays signed in. The link behind the button works once and only for 60 seconds; if a tab shows This link has expired, click SOGo again from the mailbox list.
The button is for the platform admin only: domain admins keep mailcow's own SOGo button. Inactive mailboxes are refused.
What is logged: every use appears in mailcow's login log of that mailbox as an SSO login, and in the Toolkit audit log (who opened which mailbox, when and from which address).
Updating mailcow
With a valid license, System > Updates (Toolkit) shows below the License section:
- the installed version and the last check (Up to date or Update available, with the new changes);
- the last run (running, done or failed) with its message;
- the log of the last update.
Check now looks for a new version right away; otherwise the server checks once a day.
Update now is available when the last check found a new version. After you confirm, it runs, in this order:
- a backup of crypt, redis, rspamd, postfix and mysql into your backup folder, with mailcow's own backup script; backups older than 14 days are removed. If the backup fails, the update stops and nothing is changed;
- mailcow's
update.sh; - a wait of up to 5 minutes until every container is running and the UI answers.
During the update the containers restart, so mail and the UI are unavailable for a few minutes. The page keeps showing the log and reloads by itself when the server is back. When the update finishes, an e-mail is sent to the notification address.
Notifications
On the same page, under Notifications, enter the address that should receive the e-mails and save. The server then sends one e-mail per new mailcow version (not one every day) and one when an update finishes. An empty address turns the e-mails off.
DNS & Cloudflare
Open System > DNS & Cloudflare (admins only). Without a valid license the page shows only a notice and nothing runs in the background.
Domain status
At the top, a summary row (how many domains are critical or have warnings) and a Domain selector with every mailcow domain and alias domain, worst first, each with its status. The page remembers the last domain you selected.
- critical — mail cannot be delivered or sent, or will land in spam: the A (or AAAA) record of the mail host, MX, SPF, DKIM or DMARC missing, wrong or proxied; no DKIM key in mailcow; a wrong reverse DNS of the server; or the domain not known to SOGo yet (see SOGo and new domains below).
- warning — optional records missing: autodiscover, autoconfig, SRV, CalDAV / CardDAV, MTA-STS, TLS-RPT.
- all good — everything the domain needs is in place.
The domain's view lists What needs attention, critical first, with why it matters and How to fix it, then the zone file, the domain's Cloudflare actions, the wizard and the full records table. The statuses are refreshed every 10 minutes in the background; Re-check now checks the selected domain at once. The settings for all domains (reverse DNS, Cloudflare credentials and automation, service ports, the wizard, SOGo) are in collapsible sections below.
The records of each domain
The records table of the selected domain shows what mailcow needs for it, after mailcow's own DNS check:
- required: the A record of
MAILCOW_HOSTNAME, for the domain whose zone holds the host name (DNS-only in Cloudflare, because SMTP and IMAP cannot pass Cloudflare's proxy); MX 10 →MAILCOW_HOSTNAME; SPF; DKIM with mailcow's selector and key; DMARC; - recommended:
autodiscoverandautoconfigCNAMEs →MAILCOW_HOSTNAME(DNS-only; an A record with the server's IP instead is accepted); SRV records_autodiscover,_imaps,_submissions,_submission,_pop3s(when POP3 is allowed),_caldavsand_carddavswith apath=/SOGo/dav/TXT (when SOGo is on); with MTA-STS active for the domain in mailcow, themta-stsCNAME, the_mta-stsTXT and the TLS-RPT record_smtp._tls; - for an alias domain: MX, SPF, DKIM (the row stays unknown until mailcow has a key for it), DMARC and, with MTA-STS, the
_mta-stsand_smtp._tlsTXT records.
The SPF proposed is v=spf1 ip4:<IPv4> -all. When an SPF already exists and does not cover the server, the proposal adds the server's address to it and keeps the rest; when it relies on include or redirect, the page tells you to check it.
DMARC: a valid DMARC record you already have is kept as it is. Otherwise the proposal is v=DMARC1; p=quarantine;, with rua=mailto:dmarc@<domain> (or postmaster@<domain>) only when that address exists in mailcow. The TLS-RPT reports go to tls-rpt@ or postmaster@ of the domain.
Each row is compared with three sources: public DNS, an imported zone (if you uploaded one) and the Cloudflare zone (with credentials). The status is OK, missing, different, proxied — must be DNS-only, fix by hand (for example A records where the CNAME should be, or several SPF records) or unknown (no DKIM key in mailcow yet, or the public IPv4 not known yet).
Zone file: export and import
- Download BIND zone file saves the records of the domain as a zone file. In Cloudflare, import it from DNS > Records > Import and Export > Import DNS records: the records that must stay DNS-only carry
cf_tags=cf-proxied:false. Rows still unknown are left out. - Compare with a zone file reads a Cloudflare export (DNS > Records > Import and Export > Export) or any BIND zone file, up to 1 MB, and adds an Imported zone column to the comparison. Forget the imported file removes it.
IPv4 and IPv6
By default the Toolkit works with IPv4 only: no AAAA record, ip4: only in the SPF, and the IPv6 reverse DNS is not checked. A server that publishes AAAA without a matching IPv6 PTR gets its mail over IPv6 rejected by Gmail and others. When the IPv6 PTR of the server is set, turn on Use IPv6 (publish AAAA, SPF ip6, check the IPv6 rDNS) and save.
Service ports
The Service ports (SRV records) card sets the ports the SRV records announce: IMAPS (993), Submission (STARTTLS, 587), SMTPS (465), POP3S (995) and HTTPS (443, for autodiscover, CalDAV and CardDAV). Each field is optional (1–65535). Left empty, the port comes from mailcow.conf when it is a plain number, or is the standard port when mailcow.conf binds an address such as 127.0.0.1:12587 (a proxy or NAT is in front); an empty HTTPS uses the port of the admin page. Under each field the page shows the port in use and where it comes from. The export, the comparison and Cloudflare (manual and automatic) all use these ports.
Cloudflare credentials
The Cloudflare actions of a domain (the live column, preview and apply, Provision now) appear only when the saved credentials work now and may edit DNS in that domain's zone; otherwise the page says why (no credentials, connection refused, the zone not in the account or not in the token's zone resources, or Zone > DNS > Edit missing). Run wizard works without Cloudflare.
In the Cloudflare card, choose one of:
- API token (recommended): in Cloudflare, My Profile > API Tokens > Create Token, with Zone > DNS > Edit and Zone > Zone > Read on all zones. Add Zone > Zone > Edit only if the Toolkit may create missing zones. If such a token leaks, it can change DNS records and nothing else, and it can be revoked alone.
- Global API Key with the account e-mail: it also works, but it can do everything in the whole Cloudflare account (billing, members, every product). Use it only if you accept that risk.
Check and save checks the credentials with Cloudflare first. They are stored encrypted (AES-256-GCM) in the Toolkit's folder on your server; the key is kept in mailcow's redis, not next to the file, so a copy of the folder or of mailcow's backups does not reveal them. They are never shown again: the page shows only the type and the last 4 characters. Remove the credentials deletes them and turns automatic setup off. The credentials stay on your server and are sent only to Cloudflare's API.
Applying records to Cloudflare
- Select the domain. The required records and those shown as proxied are ticked; tick or untick the others.
- Preview changes for Cloudflare lists what would be created or updated, with the value now and after.
- Apply to Cloudflare first saves a JSON backup of the whole zone, then creates or updates only those records. Nothing is deleted. If the zone changed since the preview, nothing is applied and you preview again; a preview expires after 15 minutes.
Every backup is listed under Cloudflare backups: Download saves the JSON, Undo these changes reverts that change (updated records get their old values back; created records are deleted, only if they were not changed since). Every apply and undo is written to the Toolkit audit log.
Automatic setup of new domains
With credentials saved, turn on Configure new mailcow domains in Cloudflare by themselves. Then, within about 2 minutes of a domain or alias domain being added in mailcow (from the UI or the API), the Toolkit puts its records into the existing Cloudflare zone:
- it waits up to 10 minutes for mailcow's DKIM key; without one it sets the other records and adds DKIM later (it retries every hour);
- missing records are created and wrong ones updated; the A record of the mail host and the CNAMEs are set DNS-only;
- records it does not manage are never deleted; an existing different SPF, DMARC or TLS-RPT policy is left as it is (existing policy) and reported (the manual preview can still update it);
- a backup of the zone is saved before every change, as with the manual apply;
- if the zone is not in Cloudflare, the domain shows zone missing. With Create the zone in Cloudflare if it does not exist (needs Zone > Zone > Edit or the Global API Key) the Toolkit creates it and shows the Cloudflare nameservers to set at your registrar. When the credentials see several Cloudflare accounts, Cloudflare account for new zones picks the account (by default the first one);
- with E-mail the notification address when provisioning fails, a failure is e-mailed to the address under Notifications on the Updates (Toolkit) page; the Toolkit retries after 15 minutes.
Domains that already exist when you turn this on are left alone: use Provision now next to a domain to set it up the same way.
New domain wizard
The New domain wizard: role addresses card creates, for every new mailcow domain, the role addresses as mailcow aliases, so they do not count against the domain's mailbox limit:
- the addresses: postmaster, abuse, hostmaster, webmaster, contact, info, office, dmarc, tls-rpt, noreply, security, each one optional, plus your own local parts (comma separated);
- Destination (all role addresses): where they deliver; each domain can have its own destination in its view (empty = the global one);
dmarc@andtls-rpt@can go to a separate reports address;noreply@uses mailcow's "discard silently" (null@localhost): mail to it is accepted and dropped.
Turn on Create the role addresses for new domains by themselves (it needs a destination). The wizard runs before the Cloudflare setup, so the DMARC and TLS-RPT records of a new domain send their reports to its dmarc@ and tls-rpt@. An address that already exists as a mailbox or an alias is never touched: it is skipped and reported. Alias domains are skipped, because mailcow already maps them onto the target domain's addresses. The aliases are created through mailcow's own functions, so they appear in mailcow's log. Domains that existed when you turned the wizard on are left alone: use Run wizard next to a domain.
SOGo and new domains
SOGo reads mailcow's domains when it starts. A domain added later is unknown to SOGo, so its mailboxes get "Unauthorized" in SOGo and in the Toolkit's SOGo button until SOGo restarts (the failed lookup is also kept in memcached).
With a valid license the Toolkit checks every 2 minutes whether SOGo knows every mailcow domain. When one is missing, it restarts only the SOGo container and empties memcached, about 3 minutes after the first missing domain (so several new domains share one restart) and at most once every 10 minutes: in practice within about 3 to 10 minutes of adding the domain. Webmail sessions are interrupted for a few seconds. Every restart is written to the audit log.
While a domain waits, the DNS page shows a warning with a Restart SOGo button (the same button is in the SOGo section, with the last restart). If you tick mailcow's own Restart SOGo option when adding the domain, SOGo knows it at once and no second restart follows.
Reverse DNS (PTR)
Other mail servers check that each public IP of your server has a PTR record equal to MAILCOW_HOSTNAME, and that MAILCOW_HOSTNAME resolves back to the same IP (forward-confirmed reverse DNS). The Toolkit checks this every 6 hours for each public IPv4 (and IPv6, with Use IPv6); Check now on the DNS page runs it right away.
When the check fails, a red ! appears next to the System menu on every admin page, and a Reverse DNS (PTR) row on System > Information. Without Use IPv6, an IPv6 address never raises it.
To fix it: the PTR record of an IP belongs to whoever owns the IP, not to your DNS zone. Set it in the control panel of your hosting or VPS provider (often "Reverse DNS" or "PTR" next to the IP), or ask your ISP, to exactly MAILCOW_HOSTNAME. Then make sure MAILCOW_HOSTNAME has an A record with the same IP (DNS-only in Cloudflare).
The server finds its public addresses through ip4.mailcow.email / ip6.mailcow.email, else from its network interfaces. Behind NAT or with several addresses, list them in RDNS_IPS (see Settings).
Settings
/etc/mlw-mailcow-toolkit/<name>.conf (the default name is default) holds:
BACKUP_DIR— the backup folder (empty = no backup before updating, not recommended);BACKUP_COMPONENTS— what is backed up, by defaultcrypt redis rspamd postfix mysql;BACKUP_KEEP_DAYS— how long backups are kept, by default14;RDNS_IPS— the public IPs to check for reverse DNS, space separated, for exampleRDNS_IPS="203.0.113.10"(empty = the detected addresses);SOGO_DELAYandSOGO_MIN_GAP— the wait before restarting SOGo for a new domain and the minimum time between two restarts, in seconds (by default180and600).
Your changes to this file are kept when you install again or update the Toolkit from the License section; only BACKUP_DIR changes, and only if you pass --backup-dir. The DNS & Cloudflare choices are saved from the page, not in this file, and are kept.
Removing the Toolkit
sudo ./uninstall.sh --mailcow-dir /opt/mailcow-dockerized
It stops and removes the systemd units (timers included), the settings, the agent, the kept copy of the Toolkit and the web files, and cleans mailcow's git exclude list. The shared folder goes too, with the encrypted Cloudflare credentials and the Cloudflare zone backups: download the backups you want to keep first. Your DNS records in Cloudflare stay as they are. The mailcow backups are kept. The Sign-up and Cashier tables stay in mailcow's database, and the mailboxes, domains and domain administrators they created stay in mailcow. Add --name if you installed with one. Press Remove from this server in the License section first if you want to use the license on another server.
Common problems
The SOGo button still switches my admin session. No webmail address was given at install, the webmail address has the same host name as the UI, or SOGo login as a mailbox is not allowed (ALLOW_ADMIN_EMAIL_LOGIN=y and the Login as mailbox user permission). Run install.sh again with --webmail-url and check the two settings.
The Updates page says the host agent is not installed or not running. Run install.sh again; check systemctl status mlw-mailcow-agent-default.path.
The page shows only the License section. The license is not valid: the message at the top says why (no key yet, expired, removed or moved, or not checked for more than 7 days). For the last case, check that the server reaches https://medialab.works, then press Check now.
The update failed. The page shows at which step (backup, update.sh or the container check) and keeps the log. After a failed backup nothing was changed. After a failed update.sh or container check, read the log and fix the cause as you would after running update.sh by hand.
There is a red ! next to the System menu. The reverse DNS check failed: open System > DNS & Cloudflare, section Reverse DNS (PTR) of the server, to see which IP and why. Set the PTR at your hosting / VPS provider or ISP, then press Check now.
Cloudflare refused the credentials. Check the token's permissions (Zone > DNS > Edit and Zone > Zone > Read on all zones) or, for the Global API Key, the account e-mail. The message from Cloudflare is shown on the page.
A domain shows "zone missing". The domain is not a zone in the Cloudflare account the credentials see. Add it in Cloudflare, or turn on Create the zone in Cloudflare if it does not exist; the Toolkit retries every hour, or press Provision now.
The SPF or DMARC record was "left as it is (existing policy)". Automatic setup never replaces a policy you already have. Select the domain, tick the record and use Preview changes for Cloudflare if you want the Toolkit's value.
The mailboxes of a new domain get "Unauthorized" in SOGo. SOGo does not know the domain yet: the DNS page shows a warning; wait a few minutes for the automatic restart or press Restart SOGo.
I received no e-mail. Check that an address is saved under Notifications; one e-mail is sent per new version, not every day.