Skip to content

Mail

Note

All examples use example.com replace with your DNS domain name.

DNS

Setup DNS with DNSSEC/DANE using Google Cloud DNS.

Warning

Captive DNS services must be configured to handle DNSSEC verification or validation will break.

Disable captive DNS for mail server to test DNSSEC verification works.

Static DNS Resolvers

/etc/resolv.conf

0644 root:root

search example.com.

# Use cloudflare - does not sell user data.
nameserver 1.1.1.1
nameserver 1.0.0.1

# Enable extended DNS attributes and propagate authenticated domain trust.
options edns0 trust-ad

Set Mail Hostname

Mail servers are generally hosted on a mail subdomain to enable transparent backend changes as well as main domain separation. A mail hostname does not need to match the machine hostname to server mail.

console.cloud.google.com ➔ ctrl + o ➔ {DNS} ➔ Network Services ➔ Cloud DNS ➔ {DOMAIN} ➔ Edit

  • DNSSEC: On

console.cloud.google.com ➔ ctrl + o ➔ {DNS} ➔ Network Services ➔ Cloud DNS ➔ {DOMAIN}

  • Add standard:
    • DNS name: mail.example.com
    • Resource control type: A
    • TTL: 5
    • TTL Unit: minutes
    • IPv4 Address: {PUBLIC_IP}

See r_pufky.srv.gcp_ddns for automatically updating your public IP.

# Verify DNSSEC from mail server.

# RRSIG must appear for DNSSEC/DANE mail configuration.
delv mail.example.com.
> ; fully validated
> mail.example.com.     300 IN  A   50.39.134.131
> mail.example.com.     300 IN  RRSIG   A 8 3 300 20251210160823 20251118160823 59571 example.com. {HASH}/c {HASH}/fb k4w=

# AD (authenticated domain) must appear for DNS entry.
dig mail.example.com

# Additionally test local DNS resolver if running.
dig @127.0.0.1 mail.example.com

# ad must appear in flags.
> ; <<>> DiG 9.20.15-1~deb13u1-Debian <<>> mail.example.com
> ;; global options: +cmd
> ;; Got answer:
> ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 53402
> ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
>
> ;; OPT PSEUDOSECTION:
> ; EDNS: version: 0, flags:; udp: 512
> ;; QUESTION SECTION:
> ;mail.example.com.            IN  A
>
> ;; ANSWER SECTION:
> mail.example.com.     300 IN  A   50.39.134.131
>
> ;; Query time: 88 msec
> ;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
> ;; WHEN: Thu Nov 20 21:09:44 UTC 2025
> ;; MSG SIZE  rcvd: 59

Verify Mox resolves the same.

mox -loglevel debug dns lookup a mail.example.com

# authentic=true and 'with dnssec' must be returned to validate.
> debug: dns lookup result; pkg=dns; type=ip; network=ip4; host=mail.example.com.; resp=[X.X.X.X]; authentic=true; duration=81.866448ms
> records (1, with dnssec):
> - X.X.X.X

Traefik

Pass traffic through proxy without modification. This allows the mail server to change on the backend without needing to changing firewall rules on the router. See ACME Behind Traefik for detailed information.

Forward ports to traefik TCP: 25, 465, 587, 143, 993

/etc/traefik/traefik.yml

0644 root:root

entryPoints:
  # Defer TLS requirements to routers.
  web:
    address: ':80'
  webs:
    address: ':443'
    asDefault: true
  # Passthrough mail routing.
  smtp:
    address: ':25'
  smtps:
    address: ':465'
  submission:
    address: ':587'
  imap:
    address: ':143'
  imaps:
    address: ':993'

/etc/traefik/dynamic/mail.yml

0644 root:root

http:
  routers:
    mail_http01:
      rule: 'PathPrefix(`/.well-known/acme-challenge/`) && (Host(`mail.example.com`) || Host(`autoconfig.example.com`) || Host(`mta-sts.example.com`))'
      entryPoints:
        - 'web'
      priority: 1000
      service: 'mail_http01_service'

    mail_webmail:
      rule: 'ClientIP(`10.2.2.80`) && Host(`mail.example.com`) && PathPrefix(`/webmail`)'
      entryPoints:
        - 'webs'
      tls:
        certResolver: 'lets_encrypt'
        domains:
          - main: 'example.com'
            sans: '*.example.com'
      middlewares:
        - 'redirect_to_https'
      service: 'mail_webmail_service'

    mail_admin:
      rule: 'ClientIP(`10.2.2.80`) && Host(`mail.example.com`) && PathPrefix(`/admin`)'
      entryPoints:
        - 'webs'
      tls:
        certResolver: 'lets_encrypt'
        domains:
          - main: 'example.com'
            sans: '*.example.com'
      middlewares:
        - 'redirect_to_https'
      service: 'mail_admin_service'

  middlewares:
    redirect_to_https:
      redirectScheme:
        scheme: 'https'
        permanent: true

  services:
    mail_http01_service:
      loadBalancer:
        servers:
          - url: 'http://10.5.5.240:80'

    mail_webmail_service:
      loadbalancer:
        servers:
          - url: 'https://10.5.5.240/webmail'

    mail_admin_service:
      loadbalancer:
        servers:
          - url: 'https://10.5.5.240/admin'

tcp:
  routers:
    mail_smtp:
      rule: 'HostSNI(`*`)'
      entryPoints:
        - 'smtp'
      service: 'mail_smtp_service'

    mail_smtps:
      rule: 'HostSNI(`*`)'
      entryPoints:
        - 'smtps'
      service: 'mail_smtps_service'

    mail_submission:
      rule: 'HostSNI(`*`)'
      entryPoints:
        - 'submission'
      service: 'mail_submission_service'

    mail_imap:
      rule: 'HostSNI(`*`)'
      entryPoints:
        - 'imap'
      service: 'mail_imap_service'

    mail_imaps:
      rule: 'HostSNI(`*`)'
      entryPoints:
        - 'imaps'
      service: 'mail_imaps_service'

  services:
    mail_smtp_service:
      loadbalancer:
        servers:
          - address: '10.5.5.240:25'

    mail_smtps_service:
      loadbalancer:
        servers:
          - address: '10.5.5.240:465'

    mail_submission_service:
      loadbalancer:
        servers:
          - address: '10.5.5.240:587'

    mail_imap_service:
      loadbalancer:
        servers:
          - address: '10.5.5.240:143'

    mail_imaps_service:
      loadbalancer:
        servers:
          - address: '10.5.5.240:993'

Mox

Tip

Increase TTL for configured entries after mail service is confirmed to work.

Warning

Mox quickstart will not overwrite existing directories and files. If regenerating a quickstart configuration all directories and files must be deleted.

Generate mail configuration

Configuration and certificates are generated in quickstart. Initial passwords are logged in quickstart.log.

mox quickstart -hostname mail.example.com postmaster@example.com
See troubleshooting to resolve quickstart issues.

Update DNS Records

Use the generated mox certificates to configure DNS.

DANE TLS Associations

These only need to be created for the first hosted domain (machine based).

console.cloud.google.com ➔ ctrl + o ➔ {DNS} ➔ Network Services ➔ Cloud DNS ➔ {DOMAIN}

  • Add standard:
    • DNS name: _25._tcp.mail.example.com.
    • Resource control type: TLSA
    • TTL: 5
    • TTL Unit: minutes
    • DANE TLS Association 1: 3 1 1 {HASH}
    • DANE TLS Association 2: 3 1 1 {HASH}

Relax DMARC SPF for postmaster messages

These only need to be created for the first hosted domain (machine based).

console.cloud.google.com ➔ ctrl + o ➔ {DNS} ➔ Network Services ➔ Cloud DNS ➔ {DOMAIN}

  • Add standard:
    • DNS name: mail.example.com.
    • Resource control type: TXT
    • TTL: 5
    • TTL Unit: minutes
    • TXT data: "v=spf1 a -all"

Must use double quotes for TXT data.

Enable TLS Failure Reporting

These only need to be created for the first hosted domain (machine based).

console.cloud.google.com ➔ ctrl + o ➔ {DNS} ➔ Network Services ➔ Cloud DNS ➔ {DOMAIN}

  • Add standard:
    • DNS name: _smtp._tls.mail.example.com.
    • Resource control type: TXT
    • TTL: 5
    • TTL Unit: minutes
    • TXT data: "v=TLSRPTv1; rua=mailto:tlsreports@mail.example.com"

Must use double quotes for TXT data.

Email delivery host (this mail server)

console.cloud.google.com ➔ ctrl + o ➔ {DNS} ➔ Network Services ➔ Cloud DNS ➔ {DOMAIN}

  • Add standard:
    • DNS name: example.com.
    • Resource control type: MX
    • TTL: 5
    • TTL Unit: minutes
    • Preference and mail server 1: 10 mail.example.com.

Additional secondary mail servers may be added (e.g. aspmx.l.google.com.) to continue to accept mail on Google hosted mail until service is setup.

DKIM outgoing message signing keys

console.cloud.google.com ➔ ctrl + o ➔ {DNS} ➔ Network Services ➔ Cloud DNS ➔ {DOMAIN}

  • Add standard:

    • DNS name: 2025a._domainkey.example.com.
    • Resource control type: TXT
    • TTL: 5
    • TTL Unit: minutes
    • TXT data: "{2025A_KEY}"
  • Add standard:

    • DNS name: 2025a._domainkey.example.com.
    • Resource control type: TXT
    • TTL: 5
    • TTL Unit: minutes
    • TXT data: "{2025B_KEY}"

Add all quoted lines to TXT field.

TODO - this record MUST be updated when IP changes.

SPF Softfail

Tag any email failing SPF checks (accept mail from old mail servers).

console.cloud.google.com ➔ ctrl + o ➔ {DNS} ➔ Network Services ➔ Cloud DNS ➔ {DOMAIN}

  • Add standard:
    • DNS name: example.com.
    • Resource control type: TXT
    • TTL: 5
    • TTL Unit: minutes
    • TXT data: "v=spf1 ip4:{IP} mx ~all"

Reject DMARC failures and Request Reports

console.cloud.google.com ➔ ctrl + o ➔ {DNS} ➔ Network Services ➔ Cloud DNS ➔ {DOMAIN}

  • Add standard:
    • DNS name: _dmarc.example.com.
    • Resource control type: TXT
    • TTL: 5
    • TTL Unit: minutes
    • TXT data: "v=DMARC1;p=reject;rua=mailto:dmarcreports@example.com!10m"

Enable MTA-STS TLS Certificate Validation

console.cloud.google.com ➔ ctrl + o ➔ {DNS} ➔ Network Services ➔ Cloud DNS ➔ {DOMAIN}

  • Add standard:
    • DNS name: mta-sts.example.com.
    • Resource control type: CNAME
    • TTL: 5
    • TTL Unit: minutes
    • Canonical name: mail.example.com.

console.cloud.google.com ➔ ctrl + o ➔ {DNS} ➔ Network Services ➔ Cloud DNS ➔ {DOMAIN}

  • Add standard:
    • DNS name: _mta-sts.example.com.
    • Resource control type: TXT
    • TTL: 5
    • TTL Unit: minutes
    • TXT data: "v=STSv1; id=20251120T223212"

Enable Client Autodiscovery

console.cloud.google.com ➔ ctrl + o ➔ {DNS} ➔ Network Services ➔ Cloud DNS ➔ {DOMAIN}

  • Add standard:

    • DNS name: autoconfig.example.com.
    • Resource control type: CNAME
    • TTL: 5
    • TTL Unit: minutes
    • Canonical name: mail.example.com.
  • Add standard:

    • DNS name: _autodiscover._tcp.example.com.
    • Resource control type: SRV
    • TTL: 5
    • TTL Unit: minutes
    • SRV data 1: 0 1 443 mail.example.com.

Enable IMAP Autodiscovery

console.cloud.google.com ➔ ctrl + o ➔ {DNS} ➔ Network Services ➔ Cloud DNS ➔ {DOMAIN}

  • Add standard:

    • DNS name: _imaps._tcp.example.com.
    • Resource control type: SRV
    • TTL: 5
    • TTL Unit: minutes
    • SRV data 1: 0 1 993 mail.example.com.
  • Add standard:

    • DNS name: _submissions._tcp.example.com.
    • Resource control type: SRV
    • TTL: 5
    • TTL Unit: minutes
    • SRV data 1: 0 1 465 mail.example.com.

Note trailing S signifies encryption.

Disable Unencrypted Submission Discovery

Extend DNS lifetimes as these services should never be enabled again.

console.cloud.google.com ➔ ctrl + o ➔ {DNS} ➔ Network Services ➔ Cloud DNS ➔ {DOMAIN}

  • Add standard:

    • DNS name: _imap._tcp.example.com.
    • Resource control type: SRV
    • TTL: 1
    • TTL Unit: weeks
    • SRV data 1: 0 0 0 .
  • Add standard:

    • DNS name: _submission._tcp.example.com.
    • Resource control type: SRV
    • TTL: 1
    • TTL Unit: weeks
    • SRV data 1: 0 0 0 .
  • Add standard:
    • DNS name: _pop3._tcp.example.com.
    • Resource control type: SRV
    • TTL: 1
    • TTL Unit: weeks
    • SRV data 1: 0 0 0 .
  • Add standard:
    • DNS name: _pop3s._tcp.example.com.
    • Resource control type: SRV
    • TTL: 1
    • TTL Unit: weeks
    • SRV data 1: 0 0 0 .

Require Let's Encrypt Certificates for TLS Signatures

console.cloud.google.com ➔ ctrl + o ➔ {DNS} ➔ Network Services ➔ Cloud DNS ➔ {DOMAIN}

  • Add standard:
    • DNS name: example.com.
    • Resource control type: CAA
    • TTL: 5
    • TTL Unit: minutes
    • Certificate Authority Authorization 1: 0 issue "letsencrypt.org"

Verify DNS Records

# Confirm DNS propagated for DNSSEC/DANE.
dig +dnssec +noall +answer +multi _25._tcp.mail.example.com. TLSA
_25._tcp.mail.example.com. 300 IN   TLSA 3 1 1 (
                {HASH}
        )
_25._tcp.mail.example.com. 300 IN   TLSA 3 1 1 (
                {HASH}
        )
_25._tcp.mail.example.com. 300 IN   RRSIG TLSA 8 5 300 (
                20251210160823 20251118160823 59571 example.com.
                {HASH}
        )

Confirm Configuration

TODO - setup letsencrypt

!!! abstract /data/mail/mox/config/mox.config 0660 mox:mox ``` yaml Listeners: internal: # Internal/Private IP's only. IPs: - 127.0.0.1 - ::1 public: IPs: # Use 0.0.0.0 and :: for all IPv4/IPv6 addresses. # # This should be your host IP. - {IP}

# Update whenever public IP changes.
    NATIPs:
        - {EXTERNAL_IP}

    # Use HTTP
    WebserverHTTP:
        Enabled: true

    # Use HTTPS
    WebserverHTTPS:
        Enabled: true

TODO: /data/mail/mox/mox.config NEEDS TO BE UPDATED WHEN PUBLIC IP CHANGES!.

Reference1