Skip to content

fix(mail): localhost/self SMTP relay uses direct-MX (no relay-to-itself loop)#23

Merged
nechodom merged 1 commit into
mainfrom
fix/postfix-localhost-relay-loop
Jul 1, 2026
Merged

fix(mail): localhost/self SMTP relay uses direct-MX (no relay-to-itself loop)#23
nechodom merged 1 commit into
mainfrom
fix/postfix-localhost-relay-loop

Conversation

@nechodom

@nechodom nechodom commented Jul 1, 2026

Copy link
Copy Markdown
Owner

The bug (from your mailq)

(mail for localhost loops back to myself)
    matej@nechodom.cz

Relay host = localhost → Hyperion wrote postfix relayhost=[localhost]:25 → postfix relays through itself → every message defers with "mail for localhost loops back to myself". The panel shows "sent" because postfix returned 250 on submission, but nothing is ever delivered and it piles up in the queue.

Fix

A relay host that points at this node (localhost / loopback IP / our own short or FQDN hostname) isn't a smart-host — "send via the local postfix" is direct-MX delivery. New postfix::host_is_local(); both decision points route such a config to ensure_direct_delivery_config() instead of ensure_relay_config():

  • the mta-reconfigure RPC (service.rs), and
  • the agent-boot apply (main.rs) — so a restart can't reintroduce the loop.

Note for real delivery

This stops the loop, but direct-MX from a VPS usually still won't reach an inbox (blocked port 25 / no SPF-PTR-DKIM). For mail that actually lands, point the relay at a real provider (Postmark/Mailgun/SendGrid/Brevo/Gmail) in Settings → Mail.

Test

clippy -D warnings clean; new host_is_local unit test (localhost / 127.x / ::1 / short + FQDN self vs. real relays) + adapters suite green.

🤖 Generated with Claude Code

…self

Setting the SMTP relay host to localhost (e.g. via "Detect local SMTP relay",
which pre-fills localhost:25) made Hyperion configure postfix with
relayhost=[localhost]:25 — i.e. relay through itself. postfix then defers every
message with "mail for localhost loops back to myself", so the panel shows
"sent" (postfix returned 250 on submission) but nothing is ever delivered and
the mail sits in the queue forever.

A relay host that points at THIS node (localhost / a loopback IP / our own
short or fully-qualified hostname) isn't a smart-host at all — sending "via the
local postfix" IS direct-MX delivery. Add postfix::host_is_local() and route
such a config to ensure_direct_delivery_config() instead of ensure_relay_config
at BOTH decision points: the mta-reconfigure RPC (service.rs) and the agent-boot
apply (main.rs), so an agent restart can't reintroduce the loop.

clippy -D warnings clean; new host_is_local unit test + adapters suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@nechodom nechodom merged commit 577ec2d into main Jul 1, 2026
1 check passed
@nechodom nechodom deleted the fix/postfix-localhost-relay-loop branch July 1, 2026 07:53
nechodom added a commit that referenced this pull request Jul 2, 2026
…op could return (#27)

The boot-time postfix self-heal decided smart-host vs direct-MX by calling
host_is_local(smtp_host, agent_hostname) with the SHORT hostname (`hostname`),
while the runtime mta_reconfigure path correctly uses `hostname -f`. So a relay
set to the node's OWN fqdn (e.g. `s4.example.com` on host `s4`) slipped past
host_is_local at boot into relayhost=[s4.example.com] — the exact
"mail for localhost loops back to myself" self-relay loop #23 fixed. And since
EmailConfigSet self-restarts the agent, that buggy boot path re-ran after every
mail save, silently undoing the correct decision mta_reconfigure had just made.

Fix:
- Boot resolves the FQDN once (via `hostname -f`, short-name fallback) BEFORE
  the smart-host decision and passes it to host_is_local — matching the runtime
  path. Also removes the now-duplicate `hostname -f` block in the direct-MX arm.
- Defense in depth: host_is_local also catches a relay typed as our full fqdn
  when we only know our SHORT name (degraded `hostname -f`), via a short-label
  compare scoped to that case only — a legit external relay sharing our short
  label (our mail.acme.com vs relay mail.sendgrid.net) is still a smart-host
  once we know our real fqdn.

clippy -D warnings clean; host_is_local self/loopback + degraded-short-fqdn
unit tests green.

Co-authored-by: mkn <matej@nechodom.cz>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant