Feature Specification: Component smtp

Created: 2026-10-07

Status: Implemented

Input: Reconstructed from the code at 093e68b6f (core/components/shared/smtp/default.nix, core/setups/hosts-common.nix, core/setups/wiring/smtp-routing.nix, core/setups/groups/hosting-provider.nix, core/setups/groups/operator.nix, core/resources/smtp/), pull requests #1383, #1822, #1835 and #1852.

User Scenarios & Testing (mandatory)

User Story 1 - Relay the mail of a group (Priority: P1)

The applications of a group send mail, for example sign-up and reset mail. The smtp component runs maddy as the mail relay of the group. It declares provides = [ "smtp" ], so the node answers the mail.smtp requests of the group (see smtp). The setup sets the daemon, one submission account for each application and the generation of the passwords on this node.

Why this priority: without a relay, no application of the group can send mail.

Independent Test: contracts-smtp-maddy, integrations-smtp-dkim, integrations-smtp-reports and nix-unit-setups-smtp-routing.

Acceptance Scenarios:

  1. Given a group with an smtp machine, When the setups evaluate, Then the "mail".smtp request has enable = true, domain = "smtp.<domain>" and the instance name of the group.
  2. Given a group with authelia or lldap machines, When the setups evaluate, Then the relay gets a want for each of them, with senders from mailDomains.senders.

User Story 2 - Serve submission over TLS (Priority: P2)

A client submits mail over TLS. With the internal PKI, the certificate key has owner and group maddy, and a renewal restarts maddy.service. With ACME, a renewal reloads maddy.service (see ssl).

Why this priority: TLS protects the submission passwords.

Independent Test: contracts-smtp-maddy.

Acceptance Scenarios:

  1. Given a name smtp that ACME can challenge, When the setups evaluate, Then the ACME request reloads maddy.service.

User Story 3 - Alert on bounced mail (Priority: P3)

maddy reports a bounce only as a generated failed DSN line in its journal. The component ships no journal of its own. node-logs.nix sends the journal of maddy.service to Loki, and the maddy-failed-dsn rule in telemetry.nix alerts on it.

Why this priority: the alert helps an admin, but mail flows without it.

Independent Test: no check covers the maddy-failed-dsn rule.

Acceptance Scenarios:

  1. Given a bounced message, When maddy logs generated failed DSN, Then the maddy-failed-dsn rule fires.

Edge Cases

  • The relay has no web UI, so the component requests no admin-UI oidc forward-auth.
  • A group without an smtp machine gives no notifier to Authelia and no reset mail to lldap.

Requirements (mandatory)

Functional Requirements

  • FR-001: The component MUST declare provides = [ "smtp" ] and use pkgs.maddy.
  • FR-002: The implementation MUST request "spiffe".spiffe.enable = true, so maddy reads its rotated submission passwords through the OpenBao agent.
  • FR-003: The component MUST NOT request an oidc forward-auth, because the relay has no web UI.
  • FR-004: The setup MUST set the maddy configuration, the accounts and the password generation from the mail.smtp resource.

Key Entities

  • "mail".smtp: the request of the setup. It carries enable, domain and instanceName.
  • Submission account: one account for each application that wants mail, with a password in OpenBao.
  • maddy-failed-dsn: the alert rule on bounced mail.

Success Criteria (mandatory)

Measurable Outcomes

  • SC-001: contracts-smtp-maddy, integrations-smtp-dkim and integrations-smtp-reports pass.
  • SC-002: nix-unit-setups-smtp-routing and nix-unit-setups-lldap-mail pass.

Assumptions

  • The hosting-provider and operator groups both declare an smtp machine. Each group runs its own relay.
  • The component has no icon.
  • The provider implementation (maddy-provider.nix, external-relay.nix) is in core/resources/smtp/, not in this component.

Known Deviations

None.