Modules
Sovite, a Mail Transfer Agent written in Elixir/OTP.
Authenticated Received Chain (RFC 8617): verifying the chain of ARC sets a message carries, and sealing it with one more, so the authentication results of earlier hops survive forwarding and mailing lists.
The chain validation result. instance is the highest instance
(0 without a chain), sealers the d= of each ARC-Seal:, oldest
first.
A small in-memory cache with per-entry expiry, for example to remember clients that passed anti-abuse checks.
Scores a client address or a domain against DNS block and allow lists (RFC 5782): IP lists (DNSBL, DNSWL) and domain lists (RHSBL).
Greylisting decisions, in the style of postgrey.
Counts failures per key (for example failed logins per client address) and bans a key for a while after too many of them.
Counts events per key over sliding windows, for limits such as "100 messages per hour per client address".
Reverse DNS of a client address, and whether the forward lookup of the name confirms it (forward-confirmed reverse DNS, FCrDNS).
The Authentication-Results: header field (RFC 8601), which tells the
next hop and the user's mail client what this server found out about
a message
A cluster of BEAM nodes over Partisan, without Distributed Erlang.
Keeps joining known peers (Sovite.Cluster.Peer), and reports nodes
coming and going. Start it once Sovite.Cluster.start/1 returned.
Where a cluster node accepts peers: its name, address, and port,
written name@host:port.
Keeps an ACME certificate ([tls.acme]) issued and renewed.
The bounce service: tells senders about failed and delayed delivery, and about successful delivery when they ask for it (DSN, RFC 3461).
The sovitectl command line.
Runs Sovite as one node of a cluster ([cluster]), over
Sovite.Cluster (Partisan, with mutual TLS).
Which node of a cluster delivers what (see "Handing mail to another
node" in Sovite.Core.Cluster.Queue): place/3 decides for the
routes of a message, and hand_over/2 hands a delivery job to
another node, from a delivery worker.
The queues of a cluster's nodes. Each node delivers the mail in its
own spool (queue.directory); this process keeps copies of it on
other nodes, hands mail to the node that should send it, and hands a
node's queue to the others when it leaves.
The configuration the nodes of a cluster share: a TOML file, the same on every node, under each node's own config file.
Shares anti-abuse state between the nodes of a cluster, so that limits hold for the cluster as a whole
Loads, validates, and stores the Sovite configuration file.
A single configuration problem.
The settings of a config file as key = value lines, like Postfix's
postconf: every key of the schema, with its value from the file or its
default. Keys are dotted paths (smtp.max_message_size), with array
indexes for tables that repeat (listener[0].port). Values are TOML.
Runs the MTA as a whole: reloads the configuration, drains the server
before it stops, and reports its status. Started last by
Sovite.Core.Supervisor, so it stops first.
Sends DMARC aggregate reports (RFC 7489 §7.2), when dmarc.reports is
on.
Delivers one job (a message to a group of recipients with the same
destination). Runs in a task started by Sovite.Core.QueueManager.
Built-in greylisting ([greylist]), with Sovite.Abuse.Greylist
deciding and the greylist_entries table remembering.
Outbound IP pools ([[ip_pool]]): the source addresses mail goes out
from, with warm-up schedules and daily limits.
Reads Sovite's own log lines back, for sovitectl trace and
sovitectl report.
A summary of mail traffic from the logs, in the style of Postfix's
pflogsumm, for sovitectl report.
Configures logging from the [log] config section.
:logger handler that writes to rotating log files, in the style of
pino-roll.
:logger formatter that writes one JSON object per line.
:logger handler that sends log lines to a syslog daemon: to a local
socket such as /dev/log (journald, rsyslog, syslog-ng), or over UDP
to a remote host.
The interface routing uses to read Sovite's database tables
(Sovite.Core.Repo.Tables.*): a key goes in, a string value or nothing
comes out.
Finds the MTA-STS policies (RFC 8461) of the domains Sovite delivers
to, when mta_sts.enabled is on. Sovite.Core.Delivery applies them.
Email authentication for Sovite.Core.SMTPHandler: SPF, DKIM, ARC,
and DMARC on mail from outside, DKIM signing on mail from users, and
ARC sealing on mail this server forwards.
The Prometheus metrics of the MTA, served at /metrics on the admin
server (see Sovite.Core.Web) with metrics.enabled.
The mail filters ([[milter]]) of an SMTP session, such as Rspamd,
OpenDKIM, OpenDMARC, or ClamAV-milter, run with Sovite.Milter as
Postfix runs smtpd_milters.
Outbound abuse protection ([outbound]): detects accounts that are
probably compromised, from how much of the mail they send fails.
check_policy_service ADDRESS in the restriction chains: asks a
Postfix policy server (https://www.postfix.org/SMTPD_POLICY_README.html)
such as postgrey or policyd-spf, with Sovite.Policy.Client.
Reads Postfix's main.cf and expands parameter values the way Postfix
does, for Sovite.Core.Postfix.Migration.
Reads Postfix's master.cf, for Sovite.Core.Postfix.Migration.
Turns a Postfix configuration into a Sovite one, for
sovitectl migrate postfix.
Reads Postfix lookup tables, for Sovite.Core.Postfix.Migration.
Writes the TOML of a generated config file, for
Sovite.Core.Postfix.Migration. Only what a Sovite config needs:
strings, integers, booleans, arrays of those, inline tables, tables,
and arrays of tables, with comments.
Reads the queue directory for sovitectl queue and the admin API:
lists of queued messages, and the details of one. Everything returned
is plain maps, strings, and numbers, ready to be encoded as JSON.
Schedules queued messages for delivery, like Postfix's qmgr.
Recipient expansion and validation.
Sovite's database, through Ecto.
An access rule for the restriction chains (Sovite.Core.Restrictions):
when the client address, EHLO name, sender, or recipient (kind)
matches pattern, take action (ACCEPT, CONTINUE, REJECT,
DEFER, DISCARD, HOLD, WARN, or a 4NN/5NN reply code) with
optional text.
An address rewrite: addresses matching pattern (a full address,
@domain, or a local part) become replacement (a full address,
@domain to change only the domain, or a local part to change only the
local part). kind says which addresses: :sender, :recipient, or
:both.
An alias: mail for address goes to its destinations instead.
address is a full address, @domain (every address of the domain
that has no alias of its own), or a bare local part (for local domains).
One address an alias delivers to.
A BCC rule: messages whose sender (kind: :sender) or one of whose
recipients (kind: :recipient) matches pattern (a full address, or
@domain) are also sent to address.
The DMARC evaluation of one received message, kept until the next
aggregate report for its policy domain (Sovite.Core.DMARCReports).
One DKIM signature result of a Sovite.Core.Repo.Schemas.DMARCReportEntry,
stored inside it.
A domain this server handles, and its class (see
Sovite.Core.Routing): :local, :aliased, :hosted, or :relay.
A greylisting triplet (client network, sender, recipient), see
Sovite.Abuse.Greylist: when it was first and last seen, and when it
passed. Deleted after expires_at.
How many messages went out from one source address of an IP pool on
one day (UTC), for warm-up schedules and daily limits, see
Sovite.Core.IPPools.
The cached MTA-STS policy of a domain Sovite delivers to (RFC 8461
§5.1), kept by Sovite.Core.MTASTS until expires_at.
A mailbox in a virtual mailbox domain: address, or @domain to
accept every address of the domain.
A user who moved: mail for address is rejected with 5.1.6 and
new_location (usually the new address).
A sender address a user may use in MAIL FROM: a full address
(sales@example.com), every address at a domain (@example.com), or
any address (*).
How mail from sender (an address, or @domain) leaves: through
relayhost, from source_address, logging in with username and
password. Each part is optional.
The TLS outcome of one outgoing SMTP session to a domain's MX host,
kept until the next TLS-RPT report (RFC 8460) for the domain
(Sovite.Core.TLSReports).
A transport map entry: mail for pattern (an address, a domain,
.domain for its subdomains, or *) goes through transport, a
Sovite.Core.Transport specification.
A user who can authenticate (SMTP AUTH), with the sender addresses they may use.
Access rules for the restriction chains, stored in Sovite's database
and managed with sovitectl access.
Address rewrites stored in Sovite's database, managed with sovitectl rewrite (see Sovite.Core.Rewrite).
Aliases stored in Sovite's database, managed with sovitectl alias.
BCC rules stored in Sovite's database, managed with sovitectl bcc
(see Sovite.Core.Recipients.bcc/3).
DMARC evaluations waiting to be reported, one per message, written by
Sovite.Core.SMTPHandler and read and removed by
Sovite.Core.DMARCReports when it sends the aggregate reports.
Keeps the domains of Sovite.Core.Repo.Tables.Domains in :persistent_term, so
every recipient check can read them without a database query.
Domains stored in Sovite's database, managed with sovitectl domain.
They add to the domains in the [domains] config section; a domain in
the config file wins over the same domain in the database.
The greylisting triplets seen by Sovite.Core.Greylist: one per
client network, sender, and recipient.
Messages sent per source address and day, for the warm-up schedules
and daily limits of Sovite.Core.IPPools.
The MTA-STS policies of recipient domains, cached by
Sovite.Core.MTASTS: one per domain.
Mailboxes of virtual mailbox domains, stored in Sovite's database and
managed with sovitectl mailbox.
Users who moved, stored in Sovite's database and managed with
sovitectl moved. Mail for them is rejected with 5.1.6 and
their new location.
Sender-dependent relaying, stored in Sovite's database and managed
with sovitectl sender-relay: for a sender address or @domain, the
relay host, the source address to connect from, and the credentials
for the relay host.
TLS outcomes of outgoing sessions waiting to be reported, one per
session, written by Sovite.Core.Delivery and read and removed by
Sovite.Core.TLSReports when it sends the TLS-RPT reports.
Transports stored in Sovite's database, managed with
sovitectl transport.
Users stored in Sovite's database (Sovite.Core.Repo), and a
Sovite.SASL.Backend that authenticates against them.
Restriction chains ([restrictions]): lists of checks run at each
stage of an SMTP session.
Address rewriting.
Shows how the routing tables resolve an address, for sovitectl route
and the admin API: the steps a recipient goes through when it is
received (Sovite.Core.Rewrite, Sovite.Core.Recipients) and when it
is delivered (Sovite.Core.Router).
Decides, at delivery time, where each recipient's mail goes.
The routing configuration ([domains], [routing], and the next-hop
settings of [delivery]) together with Sovite's routing tables in the
database, and the address helpers shared by rewriting
(Sovite.Core.Rewrite), recipient expansion (Sovite.Core.Recipients),
and next-hop selection (Sovite.Core.Router).
The MTA's Sovite.SMTP.Server.Handler: relay control, restrictions,
authentication, recipient checks and expansion, and durable spooling.
Anti-abuse checks of SMTP clients, run by Sovite.Core.SMTPHandler
before its own checks of each stage. Clients in
smtp.trusted_networks skip all of them.
Sender login maps: which MAIL FROM addresses an authenticated user
may use.
The sendmail(1), mailq(1), and newaliases(1) commands, for local
programs and cron jobs that expect them.
Root supervisor of the Sovite MTA.
Sends TLS-RPT reports (RFC 8460), when tls_rpt.reports is on.
The catalog of :telemetry events emitted by Sovite, and the default
handler that turns them into log lines.
Tenants ([[tenant]]), for hosting the mail of several customers on
one server or cluster, each with its own limits, signing keys, and
outbound IP pool.
Follows a message through the logs, from the connection it came in on
to its final delivery, for sovitectl trace.
The OpenTelemetry traces of the MTA, sent with tracing.enabled to the
OTLP/HTTP collector at tracing.endpoint (Sovite.Tracing).
Transport specifications: transport:nexthop.
The web server: the status UI and the admin API, served with Plug and
Cowboy on admin.address and admin.port (127.0.0.1:9025 by
default), with TLS from the certificate store when admin.tls is set.
The admin API, under /api on the web server (Sovite.Core.Web),
for sovitectl, monitoring, and the UI. Every request needs the token
from admin.token_file
DKIM signing and verification (RFC 6376), with RSA-SHA256 and Ed25519-SHA256 (RFC 8463). RSA-SHA1 signatures and RSA keys shorter than 1024 bits are never accepted (RFC 8301).
Body hashes (RFC 6376 §3.4.3, §3.4.4, §3.7), computed while the body streams by, for any number of signatures at once.
Header canonicalization (RFC 6376 §3.4.1 and §3.4.2), and the header data a signature covers (§3.7, §5.4.2).
DKIM public keys: the key records published at
<selector>._domainkey.<domain> (RFC 6376 §3.6.1), and the signature
algorithms that use them.
The outcome for one signature. b is the start of the signature, to
tell signatures apart in Authentication-Results: (header.b, RFC
6008).
A parsed DKIM-Signature: field (RFC 6376 §3.5), or the
ARC-Message-Signature: field that uses the same tags (RFC 8617 §4.1.2).
A private key to sign with, for one domain and selector, and the key record to publish for it.
DKIM tag lists (RFC 6376 §3.2): tag=value pairs separated by ;,
as used by DKIM-Signature: fields, key records, and the ARC fields.
Verifies the DKIM-Signature: fields of a message whose body is
hashed as it streams by (RFC 6376 §6).
DMARC (RFC 7489): checks that the domain in a message's From: field
is the one SPF or DKIM authenticated, and finds what its owner asks
receivers to do with mail that fails.
A discovered DMARC policy: the record, the domain it was found at
(the From domain or one of its parents), and the From domain's
Organizational Domain. See Sovite.DMARC.discover/2.
A DMARC policy record (RFC 7489 §6.3), as published in TXT at
_dmarc.<domain>.
DMARC aggregate reports (RFC 7489 §7.2 and Appendix C, with the
<version> and <np> elements of the DMARCbis aggregate reporting
draft).
The outcome of Sovite.DMARC.check/3.
DNS lookups through a pluggable Sovite.DNS.Resolver.
Default Sovite.DNS.Resolver, built on OTP's :inet_res.
Finds the hosts that accept mail for a domain (RFC 5321 §5.1).
Behaviour for DNS resolvers.
Builds delivery status notifications (RFC 3464), the messages that tell a sender their mail was not delivered (or not yet), or was delivered when they asked to know (DSN, RFC 3461).
Internationalized domain names (IDNA2008, RFC 5890–5893): conversion
between U-labels (bücher.example) and A-labels
(xn--bcher-kva.example), and their validation.
The IDNA2008 rules for code points in U-labels: the derived property of RFC 5892, the contextual rules of its Appendix A, and the Bidi rule of RFC 5893.
Punycode (RFC 3492): encodes a string of Unicode code points as ASCII
letters, digits, and hyphens, for the A-labels of internationalized
domain names (xn-- followed by the encoding).
A small layer over OTP's :eldap: connecting with StartTLS or LDAPS,
binding, searching, and running all of it in a process of its own.
Parses RFC 4515 LDAP search filters into :eldap filters, with
placeholders filled in after parsing.
A TCP listener with an acceptor pool and connection limits.
Behaviour for the process that serves one accepted connection.
Delivers messages into Maildir folders (https://cr.yp.to/proto/maildir.html).
Rewrites the addresses in an RFC 5322 address list (the value of
From:, To:, Cc:, and similar fields) and keeps every other byte:
display names, comments, groups, and folding.
RFC 5322 §3.3 date-time formatting, as used in Date: and Received:.
The header section of a message (RFC 5322 §2.2), as a list of fields that keeps every byte of the original, so unchanged fields are passed through exactly (folding and all), which matters for signatures.
Builds Message-ID: values (RFC 5322 §3.6.4).
Builds Received: trace header fields (RFC 5321 §4.4).
Trace header fields and the loop checks that read them.
Metrics computed from :telemetry events and kept in memory, for
export in the Prometheus text format with Sovite.Metrics.Prometheus.
A metric definition, built by Sovite.Metrics.counter/2,
Sovite.Metrics.sum/2, Sovite.Metrics.last_value/2 or
Sovite.Metrics.distribution/2. See Sovite.Metrics for the options.
Encodes metric families, as Sovite.Metrics.collect/1 returns them, in
the Prometheus text exposition format, version 0.0.4.
A milter client: passes SMTP sessions to a mail filter such as Rspamd, OpenDKIM, OpenDMARC, or ClamAV-milter over Sendmail's milter protocol (version 6), with the semantics of Postfix's milter support.
Applies a milter's header modifications to the header fields of a
message, with libmilter's index rules, and turns fields into the name
and value that Sovite.Milter.header/3 sends.
Encodes and decodes milter protocol packets (Sendmail's libmilter,
protocol version 6) in both directions: commands from the MTA, and the
milter's responses. Sovite.Milter uses it as the MTA side; a milter
written in Elixir can use it the other way round.
IP address and CIDR network helpers.
Runs an external command with a file as its standard input, for
delivery to programs such as procmail, dovecot-lda, or a list
manager.
The Postfix SMTP access policy delegation protocol (https://www.postfix.org/SMTPD_POLICY_README.html), for asking policy servers such as policyd-spf and postgrey what to do with an SMTP client, and for writing such servers in Elixir.
Policy server actions: the value of the action attribute of a reply,
as in a Postfix access(5) table.
Asks a policy server what to do, over TCP, a Unix socket, or the standard input and output of a program.
Behaviour for a policy server written in Elixir, run by
Sovite.Policy.Server.
A policy server: a Sovite.Listener whose connections speak the policy
delegation protocol, with the decisions made by a
Sovite.Policy.Handler.
The HAProxy PROXY protocol, versions 1 (text) and 2 (binary), which a proxy or load balancer sends first on a connection to pass on the original client and server addresses.
CRC-32C (Castagnoli, RFC 3720 / RFC 4960 Appendix B), the checksum of
PP2_TYPE_CRC32C. Erlang only has the IEEE CRC-32 built in.
A parsed PROXY protocol header, see Sovite.ProxyProtocol.
Retry delays for deferred messages (RFC 5321 §4.5.4.1).
The delivery state of a queued message: its envelope, plus the
Sovite.Queue.Records appended so far, replayed in order.
The envelope of a queued message: who sent it, to whom, and how it arrived.
Queue IDs: 14 characters from [0-9A-Za-z], safe in file names.
Delivery records, appended to a queue file after the message by
Sovite.Queue.Spool.append/3.
Durable storage for queued messages.
SASL authentication (RFC 4422), server and client side.
Behaviour for credential backends used by Sovite.SASL.Server.
A Sovite.SASL.Backend for OAUTHBEARER: checks bearer tokens with
OAuth 2.0 token introspection (RFC 7662) at the identity provider.
A Sovite.SASL.Backend that checks passwords by binding to an LDAP
directory as the user.
A Sovite.SASL.Backend that reads users from a file, one per line
Hands SASL authentication to a Dovecot auth server, over its client
protocol (version 1.2), like Postfix's smtpd_sasl_type = dovecot.
The LOGIN mechanism (draft-murchison-sasl-login): the server asks for
Username: then Password:. Obsolete, but some clients (older Outlook
versions) offer nothing else. Only safe over TLS.
The OAUTHBEARER mechanism (RFC 7628): the client sends an OAuth 2.0
bearer token (RFC 6750) instead of a password.
Stored password hashes: creating them and checking passwords against them.
The PLAIN mechanism (RFC 4616): [authzid] NUL authcid NUL password
in one message. Only safe over TLS.
The SCRAM-SHA-256 mechanism (RFC 5802, RFC 7677).
Runs the server side of a SASL exchange against a Sovite.SASL.Backend.
SMTP (RFC 5321) building blocks.
An SMTP client (RFC 5321) for relaying messages to another server.
Parses SMTP command lines (RFC 5321 §4.1).
The parameters of the SMTP DSN extension (RFC 3461 §4): NOTIFY and
ORCPT on RCPT, RET and ENVID on MAIL.
Streaming decoder for SMTP DATA content (RFC 5321 §4.1.1.4, §4.5.2).
Streaming encoder for SMTP DATA content, the inverse of
Sovite.SMTP.DataDecoder.
Values of the MAIL and RCPT parameters of these SMTP extensions
An SMTP reply: a code, an optional enhanced status code (RFC 3463), and one or more text lines.
An SMTP server: a Sovite.Listener whose connections run
Sovite.SMTP.Server.Session.
Runs a Sovite.SMTP.Server.Session on an accepted socket. Started by
Sovite.Listener; see Sovite.SMTP.Server for the public API.
Behaviour for the application side of an SMTP server: policy decisions and what happens to received messages.
The SMTP server protocol as a state machine, without I/O.
The xtext encoding of ESMTP parameter values (RFC 3461 §4): + and
=, and bytes outside !..~, are written as + and two upper-case
hex digits. Used by XCLIENT, XFORWARD, and DSN ORCPT/ENVID.
SPF verification (RFC 7208).
SPF macro strings (RFC 7208 §7).
Parses SPF records (RFC 7208 §4.5, §12).
The outcome of an SPF check.
The Sender Rewriting Scheme, so forwarded mail still passes SPF: the envelope sender is rewritten into an address at the forwarder's own domain, and bounces to that address are turned back into the original sender.
The systemd service manager protocol, without libsystemd: readiness and
status notifications (sd_notify(3)), the watchdog (sd_watchdog_enabled(3),
see Sovite.Systemd.Watchdog), and socket activation (sd_listen_fds(3)).
Sends the systemd watchdog ping (WATCHDOG=1) every half watchdog
interval, for units with WatchdogSec=.
TLS settings for mail servers and clients, following BCP 195 (RFC 9325).
An ACME client (RFC 8555) for getting certificates from a CA such as Let's Encrypt, with HTTP-01 challenges.
Builds PKCS #10 certificate signing requests (RFC 2986) for ACME: the first name as the subject's common name, and every name in a subjectAltName extension request.
Answers ACME HTTP-01 challenges (RFC 8555 §8.3): a Sovite.Listener
handler that serves GET /.well-known/acme-challenge/<token> from an
ETS table of {token, key_authorization} and answers everything else
with 404.
Holds server certificates, picks one per connection by SNI (RFC 6066), and reloads them when their files change.
A certificate chain and its private key, loaded from PEM files.
DANE certificate verification for SMTP (RFC 6698, RFC 7671, RFC 7672).
SMTP MTA Strict Transport Security (MTA-STS, RFC 8461).
An MTA-STS policy (RFC 8461 §3.2), as served at
https://mta-sts.<domain>/.well-known/mta-sts.txt.
Serves MTA-STS policies (RFC 8461 §3.3): a Sovite.Listener handler
for https://mta-sts.<domain>/.well-known/mta-sts.txt, with TLS on
the accepted connection.
SMTP TLS Reporting (TLS-RPT, RFC 8460): finding where a domain wants its reports, building the JSON report, and sending it.
OpenTelemetry traces built from :telemetry events, exported to a
collector with OTLP/HTTP and JSON.
Syntax validators for the identifiers that appear in SMTP envelopes.
Mix Tasks
Runs the MTA (as mix dev.mta) and the web UI's Vite dev server (bun dev in lib/core/sovite_web) together, for development.
Runs the MTA on this machine, for development.