Adding a Domain to Someone Else's Mail Server
The task looked like a DNS job
A domain needed mail. The mail server is my own VPS running mailcow, so this is “add the domain, add the records, create the mailbox” — the kind of task that takes twenty minutes and that I have done several times.
It took longer, because two of the things I assumed about mail servers are not true, and because of one property of the internet that no amount of server configuration can change.
Inbound: four records and a real test
Inbound mail needs exactly four things, and all four live in the domain’s own DNS (Cloudflare here):
MX ninelore.com → mail.hoelee.com (priority 10)
TXT @ → v=spf1 mx -all
TXT dkim._domainkey → (the selector the MTA publishes)
TXT _dmarc → (policy + reporting address)
Configured and then tested the only way that counts: mail sent from
outside the estate (through a third-party relay) into the new address, and
then read the server’s own log line — rspamd recording
RCPT_MAILCOW_DOMAIN(ninelore.com) as the message was accepted into the
mailbox. Receiving worked on the first try. Inbound is genuinely the easy
half, and it is easy precisely because it depends only on records you
control.
Outbound is where the ground moves
Two separate walls, each with its own error, and neither is a config mistake you can fix by editing a zone file.
Wall one: the anti-spam policy that is really a missing PTR. A direct delivery attempt to the destination provider came back:
554 5.7.1 Mail rejected due to antispam policy
Before blaming the remote, I checked the usual causes: four major blocklists, all clean, and the domain’s own SPF/DKIM/DMARC all correct. The remaining explanation was the one with no DNS record in my control — the sending IP had no reverse DNS. A receiving server that cannot map the connecting IP back to a name treats it as a source it cannot vouch for, and no amount of correct SPF outweighs that.
Wall two: relaying your way around it does not work either. The obvious next move is to send everything through the outbound relay that already works. That attempt produced:
550-From header sender domain not verified (gmail.com)
Read that error carefully, because it kills a whole family of designs. The
relay enforces its verified-sender policy against the From header domain
— and a forwarded message keeps the original sender’s From. So the moment
you forward mail from an outside sender, you are asking your relay to send
as gmail.com, which it will never allow. mailcow makes this worse in a way
that is worth knowing before you try: it selects the relay by sender
domain, so a message with an outside From does not even reach the relay you
configured.
The forwarding feature that does not exist
The plan was to create the mailbox and forward a copy to my main address. That is two surprises in a row:
- mailcow has no “mailbox forwarding” setting. Writing the mailbox address
into an alias’
gotolist is rejected (is_alias_or_mailbox) because every mailbox already carries a self-alias record and the code skips the address when computing destinations. - Keeping the mailbox and forwarding requires a Sieve filter, which is a different mechanism with a different failure mode — and it lands on wall one and wall two anyway, because the message still has to leave the box.
So I enabled nothing. A forward whose outbound path cannot deliver does
not fail quietly: it sends the rejection back to the innocent original
sender — backscatter — and every one of those bounces spends the reputation
of an IP that has none to spare. The stable answer for “this new mailbox
should reach my inbox” turned out to be a POP pull on the receiving
side: my mail client fetches from mail.hoelee.com:995 and nothing has to
be sent from the VPS at all.
PTR is one per IP, not one per domain
This is the part I keep having to explain, including to myself:
- The reverse record is a property of the IP address. There is no per-domain PTR, and there is nothing to add the next time a domain is added.
- Its correct value is the name the mail server itself announces — in this
case
mail.hoelee.com, which is what postfix sends as its HELO/myhostname. The matching forward record (mail.hoelee.com → 41.216.187.102) already exists, and the pair is what receivers verify: forward-confirmed rDNS. One half without the other is worth nothing. - Every domain on that IP is judged by that same reverse record. A hundred domains, one PTR.
- Who sets it: whoever runs the reverse zone. Here the SOA for both
216.41.in-addr.arpaand187.216.41.in-addr.arpais the VPS host’s own nameserver, so the record is a provider-side change — look for an rDNS/PTR field in the VM or network panel first, and otherwise open a ticket with the exact value above.
I checked the current state rather than assuming: the forward lookup
resolves (mail.hoelee.com → 41.216.187.102) and the reverse lookup for
that IP still returns nothing. So direct delivery continues to be rejected,
and the only outbound path that works is the relay — with its
verified-sender requirement, which new domains have to satisfy there too.
Three things to expect, so you are not surprised later
- PTR is not a reputation. It removes a hard rejection; it does not make you trusted. A new IP typically needs weeks of ordinary mail before the big providers stop folding it into spam, and starting from a fresh IP means starting that clock from zero.
- PTR fixes your own outbound and does nothing for forwarding. A
forwarded message carries someone else’s From, and if that domain
publishes
p=reject— banks and large providers do — it is blocked at the far end no matter how clean your setup is. - Relaying has a per-domain precondition. If a domain sends through a relay, that domain must be registered and verified as a sender with the relay. It is an extra step per domain forever, and it is independent of everything in your own DNS.
What I would do differently
- Decide mailbox-or-forward before creating the domain. Forwarding is the option with the longest list of things that must be true; a POP pull on the receiving side has almost none.
- Check the sending IP’s reverse record before promising anyone mail. One reverse lookup would have told me which of the two walls I was about to hit.
- Treat a receiving-side pull as a first-class design, not a workaround. “The message never has to leave my server” removes three whole categories of failure at once.
- Keep the four inbound records as a checklist. That half is boring, verifiable, and was the part that worked first try.