Skip to content

Run a mail server

In this guide a superuser sets up mail for a domain on the panel node: sending and receiving for addresses like hello@example.com, with DKIM signing.

  • A superuser account.
  • A public IPv4 address on the server, and the ability to set its reverse DNS (PTR) at your provider.
  • Port 25 unblocked, inbound and outbound. Many clouds block it until you ask.
  • DNS control for the domain.
Terminal window
PANEL=https://panel.example.com
H='Authorization: Bearer gsl_xxxxxxxxxxxxxxxx'
J='Content-Type: application/json'
Terminal window
curl -sX POST $PANEL/mail/install -H "$H"
# poll until installed
curl -s $PANEL/mail/status -H "$H" # {"installed": true}

The worker installs Postfix (SMTP), Dovecot (IMAP, POP3, LMTP) and OpenDKIM, creates a vmail user, and, if ufw is present, allows TCP 25, 465, 587, 143, 993, 110 and 995, plus 80 for the mail hostname’s certificate.

Use the apex (example.com, not mail.example.com):

Terminal window
curl -sX POST $PANEL/mail/domains -H "$H" -H "$J" -d '{"domain": "example.com"}'

A sync adds the domain to Postfix, generates a DKIM key (selector gisila), detects the public IPv4 for SPF, and requests a Let’s Encrypt certificate for the mail hostname, falling back to a self-signed one until DNS is ready.

The defaults are mail hostname mail.<domain> and DMARC policy none. Change them with:

Terminal window
curl -sX PATCH $PANEL/mail/domains/{id} -H "$H" -H "$J" \
-d '{"mailHostname": "mail.example.com", "dmarcPolicy": "quarantine"}'
Terminal window
curl -s $PANEL/mail/domains/{id}/dns -H "$H" | jq '.records'

The response lists the A, MX, SPF, DKIM and DMARC records with their values. Wait for dkimConfigured: true before publishing the DKIM record. For the mail hostname’s A record, use DNS only: a proxied (CDN) record never receives SMTP.

Then set the server IP’s reverse DNS to the mail hostname at your provider. Large providers reject mail whose HELO name doesn’t match the PTR.

POST /mail/domains/{id}/sync re-runs the sync, for example after DNS propagates.

Terminal window
curl -sX POST $PANEL/mail/domains/{id}/accounts -H "$H" -H "$J" \
-d '{"localPart": "hello", "password": "<strong-password>", "quotaMb": 2048}'

Leave out quotaMb for no quota. The password is stored as a hash and never shown again. Reset it with PUT /mail/accounts/{accountId}/password.

Each mailbox in the response has connection settings for mail clients. Use the full address as the username, submission on 587 (STARTTLS) or 465 (TLS), and IMAP on 993.

Terminal window
dig +short MX example.com
dig +short -x <server-ip> # must equal the mail hostname
curl -s $PANEL/mail/health -H "$H" # cached health of the stack

Send a message to an external mailbox and check that SPF, DKIM and DMARC pass in the message headers. If health reports a problem, POST /mail/repair queues a repair, and the health snapshot shows its result (lastRepairStatus, lastRepairDetail).