Feature Specification: Resource smtp
Created: 2026-10-07
Status: Implemented
Input: Reconstructed from the code of core/resources/smtp/, the setup wiring and the SMTP integration tests at 093e68b6f, pull requests #1184 and #1205, and issue #527.
User Scenarios & Testing (mandatory)
The smtp resource gives an application an account to submit outbound mail. It wraps the smtp contract. A group either runs its own maddy relay or submits to an external relay. The shared resource rules are in the resource family spec.
User Story 1 - Give an application a mail submission account (Priority: P1)
An application must send mail, for example a password reset. It requests a submission account with a sender address. The policy gives the application one account on the relay. The consumer node gets a contract result with the host, the port, the username and a password file. A static client module of the component reads the result to configure the mailer. The request holds only data.
Why this priority: password resets and notifications do not work without outbound mail.
Independent Test: a VM test of the maddy provider; a unit test that each request is plain data; a unit test of the policy: one want per application, the result on the consumer node, the password file of each application, and the secrets of the relay node.
Acceptance Scenarios:
- Given two enabled requests from two applications, When the policy runs, Then it gives one request per application, keyed by the application name.
- Given a request from an application, When the deployment is evaluated, Then the provider side gets a want with the sender of that application.
- Given a routed request, When the consumer side is evaluated, Then the contract result holds the sender, the relay host, the submission port, and the sender as the username.
- Given the
maddyprovider, When the relay node is evaluated, Then the password of each application is in the KV store of the group. - Given the
maddyprovider and two applications on one node, When the node is evaluated, Then each application gets the rendered password file of its own account. - Given a relay with an account that the configuration does not declare, or with a password that is not the one in the password file, When
maddy-ensure-accountsruns, Then it removes that credential and that IMAP account, and sets the password from the file.
User Story 2 - Pick the relay per group (Priority: P1)
The group data states the relay choice of the group: its own maddy relay, with or without an upstream smarthost, or an external relay. The routing reads only this group data. It knows no group name, host name or secret path. A group on the external relay provides smtp without a relay node.
Why this priority: the choice decides if the group runs a mail node at all.
Independent Test: a unit test of the routing.
Acceptance Scenarios:
- Given a group on the external relay, When the routing is evaluated, Then the provider is
external-relaywith the stated host, port, account and password file. - Given a group on the external relay without a host, a username or a password file, When the routing is evaluated, Then the evaluation fails and names the missing value.
- Given a group that states no provider, When the routing is evaluated, Then the provider is
maddy. - Given a group on
maddywith a smarthost, When the routing is evaluated, Then the relay forwards through the stated host, port and account, with a password from the secrets of the group. - Given a group on
maddywithout a smarthost, When the routing is evaluated, Then the relay delivers directly to the mail exchanger of each recipient. - Given a group with an unknown provider, When the routing is evaluated, Then the evaluation fails.
User Story 3 - Sign outbound mail and accept DMARC reports (Priority: P2)
A receiver checks DKIM and DMARC. The maddy relay signs each message with one key for the organizational domain. When the operator has an email address, the relay accepts the DMARC aggregate reports and forwards them to that address.
Why this priority: the smarthost rewrites the envelope sender. Only a DKIM signature aligns with the sender header.
Independent Test: VM tests of the DKIM signature and of the report intake.
Acceptance Scenarios:
- Given DKIM is on, When the relay sends a message, Then the message carries a DKIM signature with the selector that the zone publishes.
- Given the generated DKIM key is missing, When the relay starts, Then the start fails, so the relay does not sign with a key that nobody published.
- Given report intake is on, When the relay is evaluated, Then the firewall opens the inbound mail port, and mail to the report address goes to the forward address.
- Given report intake is off, When the relay is evaluated, Then the firewall opens only the submission port.
Edge Cases
- One application states two different senders: the evaluation fails.
- Two applications state the same sender: the evaluation fails, because the sender is the account username.
- DKIM is on without a signing domain: the evaluation fails.
- The report address is not under a local mail domain, or the forward address is under one: the evaluation fails.
- An upstream change of the relay configuration makes a rewrite of that configuration miss: the evaluation fails.
- When the policy is disabled, it gives nothing.
Requirements (mandatory)
Functional Requirements
- FR-001: The policy MUST give one request per application.
- FR-002: The policy MUST give no output when it is disabled.
- FR-003: The policy MUST make the relay node run the relay daemon.
- FR-004: The policy MUST give the generated secrets of the routed provider to the relay node.
- FR-005: The policy MUST declare the relay domain as a TLS domain.
- FR-006: The
maddyprovider MUST give each application its own account and password. - FR-007: The
maddyprovider MUST use STARTTLS and password authentication to the smarthost, and MUST rewrite the envelope sender to the smarthost account. - FR-008: Without a smarthost, the
maddyprovider MUST deliver only over encrypted connections, and MUST apply DANE and MTA-STS. - FR-009: The
maddyprovider MUST take its TLS certificate from thesslcontract. - FR-010: The
external-relayprovider MUST NOT run a daemon. - FR-011: One source MUST give the sender addresses, the DKIM selector and the report address, for the relay and for the zone records.
- FR-012: The request MUST contain only data. The consumer side MUST give the request to the
smtpcontract of the node, not as an argument to a function of the request. - FR-013: The routing MUST take the provider, the smarthost and the account from the group data. It MUST NOT name a group, a host or a secret path.
- FR-014: The
maddyprovider MUST remove each credential and each IMAP account that the configuration does not declare, and MUST set the password of each declared credential from its password file on each run. It MUST refuse an empty password file.
Key Entities
- Request: the enable flag and the sender. It holds only data.
- Client module: the static module of a component that reads the contract result and the account of its application.
- Policy: the enable flag, the relay domain, the submission port, the instance name and the routing.
maddyprovider: the relay, with its smarthost, DKIM and report settings, and the list of accounts.external-relayprovider: the host, the port, the username and the password file of an external relay.- Generated secrets: one password per application, the smarthost password, and the DKIM key.
Success Criteria (mandatory)
Measurable Outcomes
- SC-001: the VM tests of the
maddyprovider, the DKIM signature and the report intake pass. - SC-002: the unit tests of the routing, the
lldapmail, the data-only requests and the policy pass. - SC-003: A password reset mail from
lldapor Authelia reaches an external mailbox with a passing DKIM signature.
Assumptions
- The wiring and the setup flow are as in 005. The request and policy model is as in 001.
- The relay is the
smtpcomponent. Its certificate comes from thesslresource. - The consumers include
ldapandoidc. Each sends from anoreplyaddress on its own mail domain. - An operator provisions the accounts on the external smarthost and seeds their passwords by hand.