Everything here is about being trusted by the mail servers you talk to. A gateway that scans perfectly but is refused at the door is worse than useless, so treat this as part of installation rather than as tuning.
This is the single highest-impact item. Many large receivers — including Microsoft 365 — reject mail outright from an IP with no PTR record, and most of the rest score it as suspicious.
PTR records live with whoever owns the IP address, so you set this in your hosting provider's control panel, not in your own DNS zone.
mx.example.com.mx.example.com has an A record pointing back to that same IP.
Both directions must agree; this is "forward-confirmed reverse DNS", and some
receivers check it explicitly.Verify from the domain page in the dashboard (Reverse DNS panel), or:
dig +short -x 203.0.113.10 # should print mx.example.com.
dig +short mx.example.com # should print 203.0.113.10
Also check your IP against the common blocklists before going live: https://multirbl.valli.org/. A residential or recently-recycled cloud IP is often already listed, and delisting takes days.
The gateway announces itself with MAIL_HOST (inbound listener and platform
outbound nodemailer EHLO via SMTP_EHLO_NAME or MAIL_HOST). It must be a
fully-qualified name that resolves — not localhost, not a bare hostname. It
should match the PTR record from step 1.
Egress workers announce EHLO with COLDFEET_EHLO_NAME in
/etc/coldfeet-egress/config.env (falls back to the VPS public IP or hostname).
Set each worker's EHLO to a name whose A/PTR matches that worker's public IP.
SPF, DKIM and DMARC authenticate the sender. None of them stop an attacker on
the network path from stripping the STARTTLS advertisement and reading mail in
transit, because a sending MTA that cannot negotiate TLS silently falls back to
cleartext. MTA-STS (RFC 8461) is what closes that: a sender that has fetched
your policy refuses to deliver without a valid TLS connection.
It needs two things per domain:
| Record | Type | Value |
|---|---|---|
mta-sts.<domain> | CNAME | your mail host |
_mta-sts.<domain> | TXT | v=STSv1; id=<policy id> |
Both are shown, with the current policy id, on the domain page and in step 2 of the onboarding wizard.
The CNAME points the policy host at the gateway, which serves the policy at
https://mta-sts.<domain>/.well-known/mta-sts.txt. nginx proxies that path to
the API, which picks the right policy from the Host header and refuses to
answer for domains it is not the MX for.
The certificate must cover the policy host. Senders validate it strictly and ignore the policy if it fails. After adding the CNAME, extend the certificate:
certbot certonly --nginx \
-d mail.example.com \
-d mta-sts.customer-one.com \
-d mta-sts.customer-two.com
sudo systemctl reload nginx
A customer who would rather not CNAME to you can host the same policy file themselves; the body is shown on the domain page.
MTA_STS_MODE in .env.production controls what senders do on failure:
testing (default) — senders report failures but still deliver. Start here.enforce — senders refuse to deliver over an untrusted connection. Move here
once TLS reports are clean.none — publish a policy that explicitly disables enforcement, used when
backing MTA-STS out. Do not simply delete the records: senders cache the old
policy for max_age (a week by default) and will keep enforcing it.MTA-STS has no feedback channel of its own, so a broken certificate looks exactly like everything working. TLS-RPT (RFC 8460) gives senders somewhere to report failures:
| Record | Type | Value |
|---|---|---|
_smtp._tls.<domain> | TXT | v=TLSRPTv1; rua=mailto:tls-rpt@<mail host> |
Reports arrive as mail to tls-rpt@<your mail host>, are parsed automatically,
and appear on the domain page. Publish this before switching MTA-STS to
enforce.
The DMARC record generated during onboarding already points rua= at
dmarc@<your mail host>. The gateway accepts mail for that address, unpacks the
attachment (gzip, zip or bare XML) and stores each record.
One-time setup on your own zone. Because the reporting mailbox is on a different domain from the one being reported on, RFC 7489 requires the mailbox's domain to opt in. Reporters that enforce this — Google among them — will otherwise refuse to send anything. Publish a wildcard once, in the zone for your mail host:
*._report._dmarc.mail.example.com. IN TXT "v=DMARC1"
Verify with dig +short TXT customer.com._report._dmarc.mail.example.com, which
should return "v=DMARC1".
Reports show up on the domain page within a day or two, and answer the question
you need before tightening policy: which senders are failing alignment? The
domain page lists them explicitly. Work through that list, then move p=none →
p=quarantine → p=reject.
Reports can also be uploaded directly, which is useful for backfilling:
curl -X POST https://your-host/api/v1/tenants/$TENANT/domains/$DOMAIN/dmarc/reports \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d "{\"filename\":\"report.xml.gz\",\"content\":\"$(base64 -w0 report.xml.gz)\"}"
Re-uploading the same file updates the existing rows instead of double-counting.
RFC 2142 requires postmaster@ and abuse@ to work for every domain you
accept mail for. Some receivers check. Add them as recipients (or leave the
domain in catch-all mode) and make sure someone reads them.
Also enrol the gateway IP in the feedback loops of the large mailbox providers, so complaints reach you rather than quietly damaging your reputation:
Coldfeet is an application SEG, not a Postfix/Amavis milter stack. The same stages still exist:
| Stage | Coldfeet |
|---|---|
| Edge | Nest SMTP + DNSBL + allow/block lists |
| Content scan | Rspamd + ClamAV + AI/URL/DLP |
| Auth | Rspamd verify; app DKIM sign + ARC seal on leave |
| Body transforms | Stationery / banners / message crypto |
| Delivery | Dovecot LMTP, domain relay, egress workers, or platform ESP relay |
Workers only handle SMTP egress after ALLOW for traffic that needs an owned
IP — inbound gateway delivery to the tenant smart host, and outbound direct MX.
They do not accept inbound mail on port 25. The main Coldfeet IP is the sole
inbound MX. Delivery via workers is direct_mx: the agent connects to the
recipient MX (or tenant smart host) and “sent” means that hop accepted the
message. For recipient MX delivery, the platform resolves an ordered MX list
and the agent tries the next host on connect/4xx (5xx stops).
For outbound mail from a verified tenant domain, admin can add SES / SendGrid
/ Mailgun / Resend / Postmark / MailerSend relay accounts. Those sends leave from the platform (pooled
authenticated SMTP); workers are not consumed. Domains can also terminate
inbound at those providers (provider_mx); see
RELAY-ACCOUNTS.md and
INBOUND-PROVIDER-MX.md. MTA-STS is suppressed for
provider-MX domains.
After the API is healthy on the install host:
docker compose ps — api healthy (not restarting).ss -lntp | grep :25 — listener on 25 on the main host only.swaks --to user@customer.domain --server <MAIN_IP> -tlso (or
openssl s_client -starttls smtp -connect MAIN_IP:25) for an onboarded domain.direct_mx send completes (admin workers UI or agent logs).For each worker IP:
COLDFEET_EHLO_NAME in /etc/coldfeet-egress/config.env to that FQDN.Platform outbound EHLO uses SMTP_EHLO_NAME or MAIL_HOST.
New or rotated worker IPs start with no reputation. Microsoft (and others) often defer or throttle them until volume and complaint rates look normal. Treat warm-up as an ops step, not an afterthought:
4.7.500 / Access denied, please try again later or similar after warm-up
basics are in place, submit the
Office 365 sender support / mitigation form for
that IP or domain.COLDFEET_EHLO_NAME on the worker). The
platform job queue owns retries and destination throttles — there is no
local Postfix queue on workers.No, not to deliver mail. tenant.mail.protection.outlook.com accepts mail
from any IP for a domain that tenant owns; that is how every gateway in front of
M365 works. A domain whose MX points at Coldfeet needs nothing configured on the
Microsoft side for inbound mail to arrive.
An inbound connector is worth adding in two situations, both of them hardening rather than plumbing:
contoso-com.mail.protection.outlook.com can deliver
straight to the tenant and skip scanning entirely. A partner connector scoped
to Coldfeet's egress IPs, combined with restricting other sources, makes the
gateway mandatory.New-client throttling (450 … new clients, hourly caps) is a separate
problem from connectors. Register worker IPs with Microsoft (SNDS / sender
mitigation — see warm-up above); an inbound connector does not lift those
limits.
Optional inbox-placement tuning once mail is flowing: enable Enhanced
Filtering for Connectors (skip listing) on a partner connector, and add
MAIL_HOST under Trusted ARC Sealers in Exchange Online so EOP can honour
Coldfeet ARC seals when DKIM/SPF no longer align after rewrite.
If mail is deferring, the connector is almost never the cause. Check TLS verification on the delivery destination first (Coldfeet defaults it off for exactly this reason), then that the destination host is right.
Track which IPs have been listed under Admin → Workers → Connector IPs.
The direction that genuinely does need connector work is the other one — a customer's users sending outbound mail through Coldfeet so that DLP, stationery and DKIM signing apply. See SMART_HOST.md.
When Coldfeet rewrites a message (banner, URL rewrite, stationery) or relays it to M365/external, the original sender’s DKIM often breaks and SPF no longer matches. Receivers then see DMARC fail even though the mail was legitimate when it arrived.
ARC (Authenticated Received Chain) lets Coldfeet attach a signed statement: “when I received this, SPF/DKIM/DMARC were X.” Later hops can trust that chain even if the original DKIM is broken.
Coldfeet:
ARC_ALLOW / ARC_REJECT).cv=fail).MAIL_HOST is set and no
key exists yet. You can also regenerate from Admin → Platform settings →
ARC sealing.cfarc):| Record | Type | Value |
|---|---|---|
cfarc._domainkey.<MAIL_HOST> | TXT | v=DKIM1; k=rsa; p=… |
ARC_SEAL_ENABLED=true (default). Sealing logs an error (and skips)
if no key is configured. Set ARC_SEAL_ENABLED=false to disable without
deleting the key. Optional: ARC_SELECTOR=cfarc.MAIL_HOST under Trusted ARC Sealers
once mail is flowing (inbox-placement tuning — see inbound connector section
above).Workers send the already-sealed EML unchanged — no agent change.
MAIL_HOST resolves and matches the PTR_smtp._tls published, TLS reports arrivingtesting, reports clean, then enforcepostmaster@ and abuse@ deliverableCOLDFEET_EHLO_NAME (if using egress fleet)cfarc._domainkey.<MAIL_HOST>; key auto-generates on boot)