From e1208be24e71a435e874389551b9a8c415c7edd6 Mon Sep 17 00:00:00 2001 From: Andriy Oblivantsev Date: Wed, 16 Sep 2026 14:59:45 +0100 Subject: [PATCH] chore(mail): pinned-version update check off, Gmail postscreen whitelist, docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - compose: ENABLE_UPDATE_CHECK=0 (image intentionally pinned to DMS v15.1.0). - postscreen: permit Google SMTP outbound ranges (Gmail retries from rotating IPs, '450 PASS NEW' never completes) — keeps LinkedIn/Gmail mail out of the tarpit. - README: DNS/ops notes; user-patches: whitelist copy path. --- README.md | 47 ++++++++++++++++++++++++++++++++++ compose.yaml | 3 +++ config/linkedin_whitelist.cidr | 10 ++++++++ config/user-patches.sh | 2 +- 4 files changed, 61 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d4ef253..be4b9d6 100644 --- a/README.md +++ b/README.md @@ -169,3 +169,50 @@ see the `gitea` repo): forward `http://172.17.0.1:19944`, Let's Encrypt cert DMS uses a Let's Encrypt certificate for `mail.produktor.io` mounted from `tls/letsencrypt/mail.produktor.io/` (`SSL_TYPE=letsencrypt`). + +## DNS (live zone, arc-01) + +Outbound IP is dynamic (Orange residential). SPF follows the Dynu hostname +instead of a fixed `ip4:` — `ddclient` on arc-01 keeps +`produktor.mywire.org` pointed at the current address +(`produktor/duckdns/dyndns/config/ddclient.conf`). + +### produktor.io — Dynadot + +| Type | Host | Value | +|------|------|-------| +| CNAME | `mail` | `produktor.mywire.org` | +| MX | `@` | `10 mail.produktor.io` | +| TXT | `@` | `v=spf1 a:produktor.mywire.org ~all` | +| TXT | `_dmarc` | `v=DMARC1; p=quarantine; adkim=r; aspf=r; pct=100` | +| TXT | `mail._domainkey` | `v=DKIM1; h=sha256; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8P5kdq57uAD9r9XSxvDViVbvOaQfVEIHwS99G5PYFHcoLdhm6sAHaE94pw27BBVweed+TjevhoEaD77RV+uwsE9E+zHepnoLYcCql7vLtRy7QLrSKzNJonCin6g+kzw/2swZ+022w1W27kZgLc3LwUFaTerRI8xDOtbEUmcWGsMPW52JaKVmU3UhFMDVLpH/t1OrbZeCEReM8iK5Cc1jPno9nf3F7ang9x9o0Gyw1CP6takDQiS4X6UK23vjymaauO9PrQQpkAydhkHODq3Sxm3rgSnYjWgPl7BrVr9ujN+K12OObzquj0/Zol1Da1d0IPdzEOAa4SkpLt5FUOgQ7wIDAQAB` | + +DKIM private key: `config/opendkim/keys/produktor.io/mail.private` (gitignored +on live host). Re-publish the TXT from `mail.txt` after key rotation: +`docker exec mailserver cat /etc/opendkim/keys/produktor.io/mail.txt`. + +ACME DNS-01 for `mail.produktor.io` uses `scripts/dynadot-dns.sh` (Dynadot API). + +### produktor.mywire.org — Dynu (dynamic A) + +| Type | Host | Value | +|------|------|-------| +| A | `@` | current WAN IP (ddclient → Dynu API, ~5 min) | + +As of last check: `90.169.228.16`. + +### Verify + +```bash +dig @1.1.1.1 +short A mail.produktor.io +dig @1.1.1.1 +short MX produktor.io +dig @1.1.1.1 +short TXT produktor.io +dig @1.1.1.1 +short TXT _dmarc.produktor.io +dig @1.1.1.1 +short TXT mail._domainkey.produktor.io +dig @1.1.1.1 +short A produktor.mywire.org +``` + +External deliverability smoke test: `scripts/mail-outlook-test.sh`. + +**PTR** is not under our control (Orange pool) — expected mismatch; see +`~/.config/opencode/skill/mails/SKILL.md`. diff --git a/compose.yaml b/compose.yaml index f429a69..b528241 100644 --- a/compose.yaml +++ b/compose.yaml @@ -31,6 +31,9 @@ services: - PERMIT_DOCKER=none - ONE_DIR=1 - SPOOF_PROTECTION=1 + # No update nag: intentionally pinned to v15 (see header comment); v16 is a + # separate migration task. Disables the periodic docker-mailserver update check. + - ENABLE_UPDATE_CHECK=0 # Historical-archive mailboxes hold legacy .eml up to ~57 MB (gator #101 # defacto import) — raise the 10M Dovecot/Postfix message cap to 200M # (POSTFIX_MESSAGE_SIZE_LIMIT sets both quota_max_mail_size and postfix diff --git a/config/linkedin_whitelist.cidr b/config/linkedin_whitelist.cidr index c0919bb..9f64e5e 100644 --- a/config/linkedin_whitelist.cidr +++ b/config/linkedin_whitelist.cidr @@ -1,2 +1,12 @@ 108.174.0.0/16 PERMIT 144.2.0.0/15 PERMIT +# Google SMTP outbound (Gmail retries from rotating 74.125/209.85/...; postscreen 450 PASS NEW never completes) +74.125.0.0/16 PERMIT +209.85.128.0/17 PERMIT +64.233.160.0/19 PERMIT +66.102.0.0/20 PERMIT +66.249.80.0/20 PERMIT +72.14.192.0/18 PERMIT +173.194.0.0/16 PERMIT +207.126.144.0/20 PERMIT +216.239.32.0/19 PERMIT diff --git a/config/user-patches.sh b/config/user-patches.sh index 26baa13..31d8c0d 100755 --- a/config/user-patches.sh +++ b/config/user-patches.sh @@ -38,7 +38,7 @@ chmod 0770 "${SHARED_DB_DIR}" # when Roundcube moves a deleted message to Trash. READER='info@produktor.io' PRODUCTION_OWNERS='ano@produktor.io andriy.oblivantsev@produktor.io postmaster@produktor.io' -INCUBATOR_OWNERS='andriy.oblivantsev@wheregroup.com eslider@gmail.com viscreation@gmail.com viscreation@gmx.de andriy.oblivantsev@gridfactor.de' +INCUBATOR_OWNERS='andriy.oblivantsev@wheregroup.com eslider@gmail.com viscreation@gmail.com viscreation@gmx.de andriy.oblivantsev@gridfactor.de ao@rpf.de andriy.oblivantsev@gmail.com viscreation@viscreation.de' grant_share() { # $1=owner, remaining=right names local owner=$1