MTA integration

xMailman integrates with an existing mail server rather than replacing it. Your MTA accepts mail for the list domain and hands it to xMailman over LMTP (or the pipe-mode Unix socket); xMailman delivers outbound mail back through your relay.

This is the step most first-time setups underestimate. Set aside time for it, and test each leg before moving on.

The flow

  1. Inbound. A message to dev@example.com arrives at your MTA. It is routed to xMailman’s LMTP server, which parses the address — dev@example.com is a post, dev-subscribe@… is a subscription request, dev-request@… is an email command, and so on.
  2. Outbound. Posts, digests, and notices go through a persistent queue and out via SMTP to your relay (smtp.host:smtp.port).
  3. The bounce reverse leg. Outbound envelope senders are VERP-encoded — a message to alice@work.com on dev@example.com leaves with envelope sender dev-bounces+alice=work.com@example.com. If delivery fails, your MTA bounces a DSN back to that address, which routes in through the same LMTP transport and is attributed to Alice’s subscription. The reverse leg is part of the loop, not a separate channel.

Which path to use

  • LMTP — the primary path, validated end to end against Postfix and exim, including the VERP bounce reverse leg. Use this unless you have a reason not to.
  • Pipe mode — a Unix socket that a mail filter program hands messages to (xmailman deliver <address>). For setups where you’d rather pipe mail than run an LMTP listener.

The LMTP server

The LMTP server listens on lmtp.listen (:8024 by default) and is unauthenticated. In production, bind it to a private interface (127.0.0.1:8024) so only your MTA can reach it — don’t leave it exposed on a public address.

Outbound transport security

The relay connection’s transport security is set with smtp.tls, in four modes:

smtp.tlsBehavior
starttls (default)Opportunistic STARTTLS — upgrade if the relay offers it, otherwise plaintext.
starttls-requiredFail the send if the relay doesn’t offer STARTTLS.
implicitTLS from the first byte — the SMTPS pattern (e.g. port 465).
nonePlaintext, no TLS.

Certificates are verified against your system roots by default. For a relay with a self-signed or private-CA certificate, set smtp.tls_insecure_skip_verify: true — off by default, use it deliberately.

Credentials are never sent in the clear. If smtp.username is set, the session must be encrypted (implicit TLS, or STARTTLS successfully negotiated), or the send fails with a clear error instead of leaking the password. And smtp.tls: none combined with smtp.username is rejected when the config loads.

# Require encryption to a relay that advertises STARTTLS (e.g. port 587)
smtp:
  host: "smtp.example.com"
  port: 587
  tls: "starttls-required"
  username: "relay"
  password: "${SMTP_PASSWORD}"

# Implicit TLS from the first byte (e.g. port 465)
smtp:
  host: "smtp.example.com"
  port: 465
  tls: "implicit"
  username: "relay"
  password: "${SMTP_PASSWORD}"

Guides

  • Postfix — the virtual_transport wiring.
  • exim — the manualroute router to an LMTP transport.
  • Pipe mode — using xmailman deliver with a pipe-based MTA config.

Testing without an MTA

To exercise xMailman’s inbound half before wiring a real MTA, set smtp.mode: sink. Outbound mail is written as files to smtp.sink_dir instead of being relayed, so you can confirm the full pipeline with nothing but the binary:

smtp:
  mode: "sink"
  sink_dir: "./mail"

Each outbound message becomes one <nanoseconds>-<recipient>.eml file with X-Envelope-From: and X-Envelope-To: headers prepended.