Documentation
¶
Overview ¶
Package mailmap implements the chassis's "mail-domain accept map" head: a tiny TCP server that speaks the Postfix tcp_table(5) protocol so the edge Postfix can ask the chassis, per recipient domain, "is this an accepted mail domain?" before relaying it to the LMTP head.
This is what lets the single public edge Postfix accept mail for ANY verified tenant domain (a stacks subdomain or a custom domain) WITHOUT a static domain list and without becoming an open relay: the chassis (which owns tenant_hostnames) is the single source of truth, and the answer auto-updates as tenants add/verify domains. Postfix wires it as
relay_domains = tcp:<this head>:<port>
Protocol (Postfix tcp_table(5)): the client sends `get <key>\n` (the key %-encoded); we reply:
- `200 OK\n` → key found → domain is accepted
- `500 <text>\n` → not found → reject_unauth_destination rejects it
- `400 <text>\n` → temp error → Postfix treats the table as unavailable and DEFERs (4xx) — never an open relay, never lost mail
If the head is down, Postfix can't connect → table unavailable → defer. We NEVER reply 200 on error/unknown (the open-relay guard): every accept is backed by a real verified tenant_hostnames row.
OFF by default. Both gates must be flipped:
- `mailmap` must appear in `--personalities`
- `--mailmap-listen-addrs` must be non-empty
The responder is unauthenticated; bind it only where the co-located Postfix can reach it (the compose network) — never a public interface.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type MailMapController ¶
type MailMapController struct {
// contains filtered or unexported fields
}
MailMapController owns the tcp_table listeners and serves the relay_domains acceptance lookup.
func NewController ¶
func NewController(ctx context.Context, pu *processor.Unit, resolver ingress.MailResolver) *MailMapController
NewController constructs (but does not start) a mailmap controller. Mirrors the other personalities' constructor shape so server.go can treat them uniformly. `resolver` is the same MailResolver the LMTP head uses, so an accept decision can never drift from a route decision.
func (*MailMapController) Start ¶
func (c *MailMapController) Start()
Start binds the configured listeners and serves the tcp_table protocol on each. The double-gate (personality string AND non-empty listen addrs) keeps the head off by default.
func (*MailMapController) Stop ¶
func (c *MailMapController) Stop()
Stop closes the listeners and any in-flight connections, then waits for the accept loops and handlers to drain.