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
- Inbound. A message to
dev@example.comarrives at your MTA. It is routed to xMailman’s LMTP server, which parses the address —dev@example.comis a post,dev-subscribe@…is a subscription request,dev-request@…is an email command, and so on. - Outbound. Posts, digests, and notices go through a persistent queue and out
via SMTP to your relay (
smtp.host:smtp.port). - The bounce reverse leg. Outbound envelope senders are VERP-encoded — a
message to
alice@work.comondev@example.comleaves with envelope senderdev-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.tls | Behavior |
|---|---|
starttls (default) | Opportunistic STARTTLS — upgrade if the relay offers it, otherwise plaintext. |
starttls-required | Fail the send if the relay doesn’t offer STARTTLS. |
implicit | TLS from the first byte — the SMTPS pattern (e.g. port 465). |
none | Plaintext, 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_transportwiring. - exim — the
manualrouterouter to an LMTP transport. - Pipe mode — using
xmailman deliverwith 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.