Adding a Domain to Someone Else's Mail Server

· 6 min read · notes emaildnsmailcowsmtpdeliverability

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:

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:

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

  1. 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.
  2. 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.
  3. 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

  1. 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.
  2. 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.
  3. 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.
  4. Keep the four inbound records as a checklist. That half is boring, verifiable, and was the part that worked first try.
Lee Teong Hoe

Lee Teong Hoe · Mr Hoelee

Full-stack developer & DevOps engineer based in Malaysia. I run my own production infrastructure and build web systems for small businesses — this blog is the living record.

Hiring? Profile & stack · Malaysian SME? Website design & development ↗ · All posts