Standalone protocol servers
Part of the configuration reference. Covers the
three single-protocol dev/pilot binaries — pop-server, imap-server,
smtp-server — for protocol work and pilots that want exactly one
listener, as an alternative to the combined
sithbitd daemon.
Standalone protocol servers
sithbitd is the production daemon, but each mail protocol also ships
as its own dev/pilot binary — pop-server, imap-server, and
smtp-server — for protocol work and pilots that want exactly one
listener. POP and IMAP serve in-memory dev accounts; the SMTP binary
logs accepted mail rather than storing it, and verifies recipients
against its [[mailboxes]] fixtures unless a grpc_endpoint points it
at a real gateway. Each follows the same
layering as the other TOML binaries: config
file pop_server.toml / imap_server.toml / smtp_server.toml in the
working directory (or the path in POP_SERVER_CONFIG /
IMAP_SERVER_CONFIG / SMTP_SERVER_CONFIG), env prefixes POP_SERVER
/ IMAP_SERVER / SMTP_SERVER, and an empty or missing file is a
runnable loopback dev instance. The shipped files are the annotated
per-key documentation, every default commented out — bar a live
exception each, so the offline dev instance works without certificates
or DNS: the POP and IMAP files set require_tls = false, and the SMTP
file sets sender_auth = "none" (it needs no require_tls line — that
key already defaults off in MX mode).
The keys sithbitd nests under [pop] / [imap] / [smtp] sit at the
top level of these files — [server], [tls], and
[auth_rate_limit] are top-level tables here, not [pop.server] and
friends. And unlike under sithbitd, each binary reads its own
[auth_rate_limit] section — it serves one protocol, so there is no
shared budget to defer to (see the cross-connection login
budget). A
misspelled key at any level fails startup naming it, the same
deny_unknown_fields contract the daemon’s sections carry. Row markers
follow the going-public
legend.
pop-server
| Key | Default | Meaning |
|---|---|---|
hostname | "localhost" | REQUIRED (public). Hostname used in CRAM-MD5 challenges and the APOP banner — the "localhost" default names the wrong host to every remote client, and a CRAM-MD5 challenge is only as good as the name it carries |
require_tls | true | REQUIRED (public). Refuse credential-bearing commands (USER/PASS, AUTH, APOP) until TLS is active (the production posture). The shipped dev file sets false so the loopback instance works without certificates — set it back to true before the listener is reachable |
enable_apop | false | Advertise APOP via a timestamp banner in the greeting (RFC 1939 §7) — same semantics and trade-off as sithbitd’s pop.enable_apop |
enable_stored_passwords | true | RECOMMENDED (public). Advertise CRAM-MD5 — same semantics as sithbitd’s pop.enable_stored_passwords. Decide the mode deliberately: it needs a plaintext-recoverable secret, and an account holding one keeps its mail unsealed at rest |
max_login_attempts | 3 | Failed logins tolerated on one connection, each refusal tarpitted (2s, then 4s, doubling) — same semantics as sithbitd’s pop.max_login_attempts |
[auth_rate_limit] | (on — 10 per 900 s, 10000 pairs) | RECOMMENDED (public). The cross-connection login budget: max_failures, window_secs, max_tracked. On by default, but a reachable listener should size the budget against the traffic it expects |
[server] | plaintext 127.0.0.1:1100 | REQUIRED (public) for bind_addr, RECOMMENDED (public) for the [server.limits] sub-table. The shared listener section: bind_addr, implicit_tls, proxy_protocol, proxy_trusted, and the [server.limits] sub-table (unprivileged stand-in for POP3’s 110; production runs implicit TLS on 995). The default binds loopback only, and proxy_trusted is one of the few settings enforced at startup: an empty list with proxy_protocol on is refused |
[tls] | (absent — plaintext) | REQUIRED (public). certs / key, each a key source holding PEM; enables STLS (and implicit TLS with server.implicit_tls). Absent, the listener is plaintext-only — which is why the dev file has to disable require_tls |
[[accounts]] | (no entries) | REQUIRED (public). Dev accounts served by the in-memory backend, one entry per account: user, secret. Each gets an empty maildrop. The shipped file carries a live fixture account whose password is written out in that file — remove it before the listener is reachable |
[health], [observability] | health on 127.0.0.1:8194 | RECOMMENDED (public). The two shared sections above |
imap-server
| Key | Default | Meaning |
|---|---|---|
hostname | "localhost" | REQUIRED (public). Named in the greeting and CRAM-MD5 challenges — the "localhost" default names the wrong host to every remote client |
require_tls | true | REQUIRED (public). Refuse LOGIN/AUTHENTICATE until the connection is protected, advertising LOGINDISABLED (the production posture). The shipped dev file sets false so the loopback instance works without certificates — set it back to true before the listener is reachable |
max_message_size | 26214400 (25 MiB) | Largest accepted APPEND literal, in octets — same semantics as sithbitd’s imap.max_message_size, down to being advertised as the APPENDLIMIT capability and named in the refusal a synchronizing literal draws |
max_login_attempts | 3 | Failed logins tolerated before the session ends with BYE, each refusal tarpitted — same semantics as sithbitd’s imap.max_login_attempts |
idle_command_timeout_secs | 1800 (30 min) | How long a client may sit silent inside an accepted IDLE before the session ends with BYE — same semantics as sithbitd’s imap.idle_command_timeout_secs, replacing this binary’s [server.limits] idle_timeout_secs for the duration of the exchange. Both default to 1800, so at shipped defaults the replacement changes nothing — it bites only where the shared deadline has been lowered, keeping an idler alive under a listener tuned for shorter reads |
[auth_rate_limit] | (on — 10 per 900 s, 10000 pairs) | RECOMMENDED (public). The cross-connection login budget: max_failures, window_secs, max_tracked. On by default, but a reachable listener should size the budget against the traffic it expects |
[server] | plaintext 127.0.0.1:1430 | REQUIRED (public) for bind_addr, RECOMMENDED (public) for the [server.limits] sub-table. The shared listener section, same fields as pop-server’s (unprivileged stand-in for IMAP’s 143; production runs implicit TLS on 993). The default binds loopback only |
[tls] | (absent — plaintext) | REQUIRED (public). certs / key, each a key source; enables STARTTLS (and implicit TLS with server.implicit_tls). Absent, the listener is plaintext-only — which is why the dev file has to disable require_tls |
[[accounts]] | (no entries) | REQUIRED (public). Dev accounts, one entry per account: user, secret. Each gets an empty INBOX — deliver test mail with APPEND. The shipped file carries a live fixture account whose password is written out in that file — remove it before the listener is reachable |
[health], [observability] | health on 127.0.0.1:8196 | RECOMMENDED (public). The two shared sections above |
smtp-server
One binary, either SMTP role per mode. Accepted mail is logged, not
stored — the store-backed delivery pipeline is sithbitd’s.
| Key | Default | Meaning |
|---|---|---|
hostname | "localhost" | REQUIRED (public). The EHLO/greeting domain, the Received: by host, the CRAM-MD5 challenge seed, and the SPF host domain — the "localhost" default is wrong on the wire for every peer, and receiving MTAs judge mail by it |
greeting | "SithBit ESMTP service ready" | Free text after the hostname in the 220 banner — same semantics as sithbitd’s |
enable_stored_passwords | true | RECOMMENDED (public). Advertise CRAM-MD5 on the submission listener — same semantics as sithbitd’s smtp.enable_stored_passwords. Decide the mode deliberately: it needs a plaintext-recoverable secret, and an account holding one keeps its mail unsealed at rest |
mode | (unset — runs as "mx") | RECOMMENDED (public). "mx" (inbound, sender policy active, no relay) or "submission" (AUTH over TLS, relay allowed). Unset behaves as "mx" here; only sithbitd distinguishes the two, filling in "submission" for a [submission] section that named no role. Name the role explicitly on a public instance: unset is "mx", so a submission service that never says so relays nothing |
local_domains | (the hostname) | REQUIRED (public). Domains accepted for local delivery; empty defaults to [hostname]. No chain discovery here — list them explicitly, or a public MX accepts mail for nothing it means to serve |
postmaster_wallet | (unset) | RECOMMENDED (public). Wallet bare/@local-domain postmaster mail delivers to, bypassing the frombox postage gate (RFC 5321 §4.5.1) — same semantics as sithbitd’s |
sender_auth | "spf" | RECOMMENDED (public). MX-mode sender authentication: "spf", "dmarc-lite", "dmarc", or "none". The shipped dev file sets "none" so offline dev skips DNS lookups — a public MX must choose a real policy; RUA/RUF aggregate reporting is not wired in this standalone binary — sithbitd does it |
dnsbl_zone | (unset — off) | RECOMMENDED (public). DNSBL zone the connecting peer’s IP is checked against at accept time. Off by default; a public MX wants one |
dbl_zone | (unset — off) | RECOMMENDED (public). Domain block list zone the sender domain is checked against at EHLO/MAIL FROM. Off by default; a public MX wants one |
grpc_endpoint | (unset — dev fixtures) | REQUIRED (public). The mail-grpc gateway for alias/frombox lookups, e.g. "http://127.0.0.1:50051" (https:// once grpc_tls is set); absent, recipients verify against the [[mailboxes]] fixtures below, which no real deployment wants |
grpc_tls | (absent — plaintext) | REQUIRED (public) once the gateway enforces [auth] — every gateway not on this host’s loopback. The server’s half of the mutual TLS, the three keys below — all or none. The endpoint must then be https://; http:// with this table, or https:// without it, refuses to start. Add the server’s key to the gateway’s authorized_keys |
grpc_tls.cert | (unset) | REQUIRED (public) with grpc_tls. The server’s PEM client certificate — a path or a key source. Its Ed25519 key is what the gateway allow-lists |
grpc_tls.key | (unset) | REQUIRED (public) with grpc_tls. Private key for cert, PEM. Same source forms |
grpc_tls.gateway_key | (unset) | REQUIRED (public) with grpc_tls. The base58 Ed25519 key in the gateway’s certificate, pinned — no CA, no hostname check; any other certificate fails the handshake |
[auth_rate_limit] | (on — 10 per 900 s, 10000 pairs) | RECOMMENDED (public). The cross-connection login budget. SMTP has no max_login_attempts sibling, so in mode = "submission" this table is the only authentication budget in the stack — all the more reason to size it against the traffic you expect |
[server] | plaintext 127.0.0.1:2525 | REQUIRED (public) for bind_addr, RECOMMENDED (public) for the [server.limits] sub-table. The shared listener section, same fields as the other two (production MX runs 25 with STARTTLS, submission implicit TLS on 465). The default binds loopback only |
[tls] | (absent — plaintext) | REQUIRED (public). certs / key, each a key source; enables STARTTLS (and implicit TLS with server.implicit_tls). Absent, the listener is plaintext-only, and submission’s own require_tls default then refuses every AUTH |
[[accounts]] | (no entries) | REQUIRED (public) in submission mode. Dev submission accounts (mode = "submission"), one entry per account: user, secret. The shipped file carries a live fixture account whose password is written out in that file — remove it before the listener is reachable |
[[mailboxes]] | (no entries) | Dev local mailboxes used when grpc_endpoint is absent, matched against RCPT addresses in local_domains: local_part, wallet, stamps (default 1), required_postage (default 0 lamports) |
[health], [observability] | health on 127.0.0.1:8195 | RECOMMENDED (public). The two shared sections above |
The standalone binary shares sithbitd’s [smtp] config shape, so the
remaining listener keys — require_tls (defaulted by mode: off for MX,
on for submission), max_message_size, max_recipients,
max_messages, max_recipient_errors, client_cert_auth,
self_service_base_url, accept_wallet_literals, and the [quota]
table — all parse here too, with the defaults documented under
sithbitd’s [smtp]/[submission]
section. Two of them are
inert by construction: the quota
gate wires no account
store here, and self_service_base_url carries only the funding link
(the DND gate is sithbitd’s). The POP and IMAP binaries likewise accept
client_cert_auth for SASL
EXTERNAL, off by default and
inert without [tls].