[smtp], la file que vous gérez, et les parcours autour des domaines d’envoi. L’assistant de premier démarrage écrit une base fonctionnelle pour tout cela.
Écouteurs et ports
L’entrant (25) et la soumission (587/465) sont des chemins de code séparés aux règles différentes : le courrier non fiable est authentifié et scoré, le courrier des utilisateurs est validé en identité puis mis en file. Ne proxifiez jamais ces ports ; ils écoutent en direct, même derrière un proxy inverse.
La session SMTP
Ce que chaque écouteur fait d’une connexion, quel que soit son port :- Un message se termine par
<CRLF>.<CRLF>, et nulle part ailleurs (RFC 5321 §4.1.1.4). La ligne à point qui clôtDATAétait aussi reconnue après un LF nu :corps<LF>.<CRLF>MAIL FROM:…terminait le message, et les octets suivants étaient lus comme une seconde transaction, avec une enveloppe forgée (contrebande SMTP, ou SMTP smuggling). Une ligne terminée par un LF nu reste acceptée comme texte du corps, comme le fait Postfix par défaut. Toute ligne qui commence par un point perd ce point (§4.5.2), et pas seulement... Un octet NUL dansDATAest refusé554 5.6.0: le corps est du texte. Une ligne de plus de 1000 octets est toujours remise intacte. - La porte entrante répond comme le dit la RFC 5321.
VRFYrépond252et ne divulgue rien (unVRFYsans argument reçoit501),EXPNetHELPreçoivent502,NOOPignore son texte,QUITavec un argument reçoit501; tous recevaient500.RCPT TOlit son chemin avec la grammaire deMAIL FROM: un chemin mal formé reçoit501 5.1.3(c’était550 No such user), un chemin sans chevrons est refusé,<Postmaster>sans domaine est accepté, et une partie locale inutilement entre guillemets vaut sa forme simple ("jo"@example.comvautjo@example.com). Une adresse non ASCII sansSMTPUTF8surMAIL FROMreçoit553 5.6.7(RFC 6531 §3.5).REQUIRETLSn’est annoncé qu’une fois la session chiffrée ; demandé sur une session en clair, il reçoit530 5.7.10(RFC 8689 §4). postmaster@etabuse@joignent chaque domaine de courrier d’une organisation (RFC 2142), pas seulement son domaine principal, sans alias fait à la main : sur une box de production, un domaine secondaire avait refusé postmaster 531 fois en trois jours.oximail doctorsignalerole_addresses, un échec qui nomme les domaines d’une organisation sans aucun compte, le seul cas encore refusé. Une erreur de lecture de la boîte de réception de la cible donne désormais451, là où elle était avalée en550.- La limite de cinq minutes est une limite d’inactivité. Chaque commande et chaque lecture du corps la réarment (RFC 5321 §4.5.3.2). Elle coupait une session cinq minutes après son ouverture, même très active. Un pair muet reçoit
421 4.4.2avant la fermeture, au lieu d’une réponse vide. Une session vit une heure au plus, et le journal dit laquelle des deux limites l’a terminée ; les deux comptent dansoximail_smtp_inbound_timeout_total. - Un
250après les données veut dire que le message survit à une coupure de courant. Juste avant cette réponse, le serveur force sur disque le journal d’écriture anticipée de la base (fdatasync, hors du verrou d’écriture : seul le client qui l’attend patiente ; 2,5 ms mesurées sur un disque de production). La base seule survit à un plantage du processus, mais une coupure de courant ou un plantage de l’hôte pouvaient annuler ses derniers commits, et la RFC 5321 §6.1 interdit de perdre un message après le250. Une synchronisation qui échoue répond451à la place : l’expéditeur réessaie, ce qui donne au pire un doublon, jamais une perte. Quand une porte SMTP est durable, le spool du relais synchronise aussi son fichier de corps et son répertoire. Voirdurable_doorsplus bas. - Un redémarrage n’accepte plus une connexion pour la couper ensuite. À l’arrêt, les écouteurs SMTP (et ceux d’IMAP et de HTTPS) cessent d’accepter : le noyau refuse ce qui arrive ensuite. Une session qui attend sa prochaine commande reçoit
421 4.3.2; unDATAou unBDATen cours se termine. La vidange attend les workers et les sessions ouvertes dans un même budget de 10 secondes ; quand il est épuisé, le journal nomme ce qu’elle attendait encore. Auparavant, les écouteurs acceptaient encore pendant l’arrêt puis coupaient ces connexions : un message stocké mais coupé avant son250pouvait être envoyé deux fois. - Plus d’attente de 40 ms par réponse. Chaque connexion acceptée désactive l’algorithme de Nagle, sur tous les écouteurs. Un
SELECT,STATUS,LISTouFETCHIMAP attendait 40 ms l’accusé de réception retardé du client (44 ms mesurées chacun, sur une box sans proxy devant). - La ligne de journal d’un message accepté dit sur quoi il a été jugé. La ligne
message acceptedporte l’adresse du client jugé (client_ip: le vrai client derrière un MX de secours) et les verdictsspf,dkimetdmarc, dans les mêmes mots que l’Authentication-Resultsstocké avec le message, à côté de l’identifiant de file, de l’expéditeur d’enveloppe, des nombres de remises réussies et échouées, du score de spam et de l’usage de TLS.
[storage] durable_doors liste les réponses qui passent par la barrière de durabilité : smtp-inbound et smtp-submission (le 250 après les données) et smb (un FLUSH sur le partage de fichiers). Les trois sont actives par défaut ; la clé existe pour en retirer une. Un nom inconnu fait refuser la configuration, et oximail_durability_barrier_seconds{door,result} montre ce que la durabilité coûte sur le disque de la box.
Domaines d’envoi et clés DKIM
Chaque domaine pour lequel le serveur envoie a sa propre clé de signature, déclarée comme entrée de tableau :oximail setup dkim --domain <d>), ajouter le bloc ci-dessus, et publier l’enregistrement DNS. Une clé sans son enregistrement TXT publié signe du courrier que les destinataires rejettent. oximail check-dns --domain <d> confirme l’ensemble des enregistrements ; la page d’authentification du courrier explique chacun.
Un domaine internationalisé est comparé sous sa forme ASCII (A-label, xn--…, RFC 8616) : un From: écrit en Unicode est signé par la clé configurée pour ce domaine, et une clé déclarée avec un domain en Unicode signe avec son A-label comme d= ; un IDN invalide dans [[smtp.dkim_keys]] arrête le démarrage. Un tel message partait auparavant sans signature. Un message qu’une règle d’utilisateur fait suivre hors du serveur n’est signé avec la clé d’un domaine hébergé que si ce serveur a authentifié son auteur (une soumission locale, un avis du serveur, ou un courrier externe qui a passé DMARC à l’entrée) ; sinon il part sans signature, si bien qu’un From: falsifié d’un domaine hébergé ne peut pas être blanchi en signature valide par une redirection.
La soumission valide à la fois le MAIL FROM d’enveloppe et le From: du corps contre les identités du compte authentifié : les utilisateurs ne peuvent pas envoyer sous des adresses qu’ils ne possèdent pas, et les adresses d’envoi supplémentaires se gèrent comme des alias (oximail alias, ADR-089). Les réponses de fin de DATA disent quelle règle a échoué : un From: qui n’est pas une identité reçoit 550 5.7.1 ; un message sans From: exploitable, ou qui porte plus d’un champ From:, Sender: ou Date:, ou plusieurs boîtes dans From: sans Sender:, reçoit 554 5.6.0 (RFC 5322 §3.6). Un message à deux champs From: n’est jamais signé, quel que soit le chemin par lequel il atteint le worker sortant. Ce que le client a omis, le serveur le complète au lieu de refuser (RFC 6409 §8) : un Date: ou un Message-ID: manquant est ajouté, et chaque message soumis porte une ligne Received: pour son saut de soumission (RFC 5321 §4.4 : la ligne est toujours là, elle compte les sauts). Par défaut, cette ligne ne nomme pas le client : [smtp] submission_trace = "private" écrit from authenticated-client, si bien que les destinataires n’apprennent plus le nom ni l’adresse du poste de l’expéditeur ; l’adresse reste dans le journal du serveur. "full" écrit le nom EHLO et l’adresse, comme pour tout autre saut, où un client IPv6 s’écrit [IPv6:…] (RFC 5321 §4.1.3). Toute autre valeur empêche le démarrage. Le relais authentifié garde sa trace complète, quoi que dise cette clé.
Router le sortant : les cartes de transport
Par défaut, le courrier sortant résout le MX de la destination et remet en direct.[[smtp.transport_maps]] surcharge le routage par motif de destination : c’est le mécanisme derrière l’envoi par relais/smarthost :
La file sortante
Chaque message livré porte sonReturn-Path: et une clause for dans la ligne Received: que le serveur ajoute, si bien que l’expéditeur d’enveloppe est écrit là où les rebonds, les désinscriptions de liste, le rejeu SPF/DMARC et une migration peuvent le lire ; la commande MAIL refuse ce qui n’est pas un reverse-path (<a@b@c>, une adresse nue sans chevrons) et les paramètres qu’elle n’implémente pas (BODY=BOGUS, SIZE=abc), selon la grammaire de la RFC 5321 §4.1.2, là où elle répondait 250 à tout.
Chaque soumission atterrit dans la file de remise persistante ; un worker réessaie avec backoff exponentiel (1 min à 24 h) jusqu’à l’expiration de la fenêtre d’abandon de 5 jours, puis renvoie un avis de non-remise RFC 3464. Gérez-la avec :
nom+tag@example.com) est remis à ce compte. La file résout désormais une adresse comme la porte d’arrivée (RFC 5233 : le tag d’abord retiré, puis les comptes, les alias et le joker du domaine), à chaque endroit où elle décide entre local et distant, y compris pour les rapports et les avis de retard renvoyés à un expéditeur sous-adressé. Elle cherchait l’adresse exacte, ne trouvait rien, envoyait le message par SMTP à ce même serveur, et se heurtait à la garde anti-boucle jusqu’à l’abandon.
Quand l’issue d’une remise ne peut pas être réécrite sur la copie de l’expéditeur (son deliveryStatus), le journal le dit : une ligne de file dont l’identifiant de compte ne se lit pas est une erreur, un Email soumis depuis une boîte détruite avant la tentative un avertissement. Les deux étaient abandonnés sans une ligne. La remise elle-même continue dans tous les cas.
Une file qui grossit sans se vider signifie presque toujours un problème de DNS, de DNS inverse ou de réputation de votre côté : commencez par oximail check-dns et par la réponse du serveur distant enregistrée sur l’entrée de file. À noter : un effacement de compte refuse volontairement de s’exécuter tant que ce compte a des entrées sortantes non remises ; les annuler par la surface de file le débloque.
Famille d’adresses et créneaux de livraison
[smtp] outbound_address_family décide de la famille d’adresses IP depuis laquelle le serveur envoie. "auto" (la valeur par défaut) prend l’IPv4 dès que la destination en a une, l’IPv6 seulement pour une destination qui n’en a pas, et jamais en repli d’une IPv4 en échec : une adresse d’envoi gagne sa réputation par famille, et une IPv6 qui n’a jamais envoyé est froide chez les grands fournisseurs, quelle que soit l’ancienneté de l’IPv4. "v4" ou "v6" impose une famille, et une destination sans adresse dans cette famille fait échouer la tentative, que le journal consigne, au lieu de prendre l’autre en silence. Toute autre valeur empêche le démarrage.
[smtp.dispatcher] règle les travailleurs du sortant. Chaque clé est facultative, les valeurs par défaut privilégient la réputation, et une machine ne renseigne la table que pour augmenter le débit sur une adresse bien chauffée ou pour le resserrer.
Limites de débit
[rate_limit] smtp_outbound_rcpt_per_hour plafonne le volume sortant par compte en destinataires par heure (150 par défaut, 0 illimité ; un message vers N adresses coûte N, en une enveloppe ou en N), la valeur d’instance dont chaque compte hérite, surchargeable par compte (oximail account quota --outbound-rcpt-per-hour, ou outboundRcptPerHour sur Principal/setAdmin ; 0 = sans limite pour ce compte) ; une soumission au-delà est différée 452 4.7.1, comptée par compte dans oximail_smtp_outbound_budget_deferred_total. L’ancienne clé smtp_outbound_per_hour, qui comptait des enveloppes, est une pierre tombale : une configuration qui la porte encore refuse de démarrer et le dit, puisque réinterpréter sa valeur en destinataires resserrerait chaque box en silence. La table [rate_limit.destination_domains] façonne le comportement par destination pour rester courtois avec les grands destinataires. Le budget de destinataires est surchargeable à l’exécution (configuration) ; une plateforme d’envoi en masse derrière un compte ordinaire le rencontre, voir envoi en masse.
MX de secours
Une seconde machine avec[mode] role = "backup" accepte le courrier de vos domaines quand le principal est à terre, dans l’un des deux modes (file ou proxy synchrone : voir la couche SMTP). L’assistant a un déroulé dédié Backup MX ; publiez-le en MX priorité 20. Un MX de secours doit connaître les destinataires valides ou rester en mode proxy : un secours qui accepte tout est une source de backscatter. Le courrier gardé pour le principal est du courrier de tiers en transit : depuis ADR-149, son corps est un fichier du segment _backup du spool du relais, et plus une colonne de la base que chaque sauvegarde et chaque instantané emportaient pour toute la rétention, et un MX de secours sans spool configuré est refusé par check-config. Le rôle de secours démarre aussi par le même chemin que les autres rôles, si bien que l’inventaire des écouteurs et les gardes de démarrage sont les mêmes.
Le secours écrit sa propre ligne Received: avant de relayer, dans les deux modes. Elle nomme l’EHLO du client, son adresse et l’usage de TLS. La RFC 5321 §4.4 l’exige, et sans elle le principal n’avait aucun moyen de savoir qui le secours avait servi.
Le principal juge le courrier relayé sur le client que le secours a servi (ADR-166). Une connexion d’un relais reconnu est jugée sur l’adresse et l’EHLO du client, lus dans sa ligne Received: la plus haute : SPF, DMARC, le contrôle DNSBL du pipeline anti-spam, la source notée pour les rapports agrégés DMARC, le sceau ARC et le bannissement par piège à spam utilisent tous cette adresse, et le message reçoit le score qu’aurait un client direct. Un relais reconnu est une adresse d’un hôte publié comme MX de l’un des domaines de courrier de la box, autre que ce serveur. Il n’y a aucun réglage : publier le MX vaut déclaration, puisque qui contrôle ces enregistrements contrôle déjà où va le courrier du domaine, et le verdict reste celui du principal. L’ensemble est dérivé avant l’ouverture du port 25 (10 secondes au plus), puis toutes les 10 minutes : un secours ajouté pendant que le serveur tourne est reconnu dans ce délai. Un domaine sans MX n’a pas de secours (son MX implicite est le domaine lui-même, RFC 5321 §5.1) et n’empêche plus de reconnaître les autres. Un hôte MX sans aucune adresse n’est pas un relais. Seul un échec du résolveur conserve l’ensemble dérivé en dernier. Un en-tête n’accorde jamais la confiance : un relais reconnu n’est jamais un pair de confiance, même listé dans [security] trusted_ips, et une ligne de trace illisible, ou une origine en boucle locale, privée ou lien-local, fait juger le relais lui-même. Un relais reconnu n’est pas limité en débit à la connexion.
Auparavant, le seul moyen de faire passer SPF au courrier relayé était de lister le secours dans trusted_ips, ce qui sautait aussi la DNSBL et le rejet DMARC : rien de ce qu’un secours relayait n’était jamais comparé à une liste. Retirez une telle entrée. oximail doctor signale backup_relays : les hôtes MX de chaque domaine de courrier, chacun « ce serveur » ou « relais reconnu » avec ses adresses, ou sans adresse et donc incapable de joindre la box ; un avertissement quand un relais reconnu figure aussi dans trusted_ips, où il est superflu pour le courrier et contourne le limiteur de débit et le fail2ban sur toutes les autres portes. Le contrôle est sauté sur un MX de secours lui-même.
Le déroulé Backup MX de l’assistant ne publie que les enregistrements du secours : un MX à côté de celui du principal, son A et son AAAA, chacun lu avant d’être créé. Il ne génère ni ne publie aucune clé DKIM : un secours ne signe rien pour le domaine, et sa configuration ne nomme aucune clé. Avec un jeton Cloudflare, ce déroulé supprimait le TXT de default._domainkey.<domaine>, la clé publique du principal, et publiait à sa place une clé inutilisée : chaque message signé par le principal échouait alors à DKIM. Il ne publie plus non plus les enregistrements SRV clients du domaine (_imaps, _submission) : les clients sont dirigés vers le principal. Ses instructions manuelles listent les mêmes enregistrements, et disent pourquoi il n’y en a pas d’autres. Un secours installé avec une version antérieure peut encore détenir une clé DKIM qu’il a générée ; elle ne signe rien, et oximail check-dns lancé sur le principal confirme que default._domainkey porte toujours la clé du principal.
check-dns et doctor demandent à un MX de secours ce dont il a besoin : un enregistrement MX du domaine qui le nomme, son PTR, DMARC, TLS-RPT et MTA-STS (dont la politique doit couvrir chaque MX, le secours compris). Ils ne lui demandent plus le SPF ni le DKIM du domaine, qui appartiennent au principal, ni les enregistrements clients.
Une box sans entrée publique
Une box peut envoyer sans être joignable : le relais sortant emporte son courrier par une route authentifiée. Recevoir est une autre affaire : un MX, un pair fédéré et un utilisateur hors du cabinet doivent tous atteindre la box. Trois situations l’empêchent : un opérateur qui partage une adresse IPv4 entre de nombreux abonnés (le CGNAT des lignes mobiles et de certaines fibres grand public), une politique d’hébergement qui refuse les connexions entrantes, et une box fermée volontairement. Une adresse fixe n’est pas nécessaire pour recevoir : un MX nomme un hôte, et un nom DNS dynamique suit une adresse qui change. Le cas qui ne coûte rien : la box a une adresse IPv6 publique. Une ligne en CGNAT en a presque toujours une. Publiez la box en MX priorité 10 avec son seul AAAA, et un MX de secours que vous exploitez ailleurs, en double pile, en priorité 20 : les expéditeurs en IPv6 joignent la box directement, ceux qui n’ont que l’IPv4 tombent sur le secours, qui garde le courrier et le livre à la box en IPv6 ([mode] primary_host). Le secours est un relais reconnu : la box juge chaque message sur le client que le secours a servi, jamais sur le secours. Les pairs fédérés et les clients joignent la box en IPv6 eux aussi ; les utilisateurs hors du cabinet qui n’ont que l’IPv4 entrent par un VPN que l’opérateur de la box exploite, et qui n’a besoin d’aucune porte publique. Le MX de secours lit le courrier qu’il garde, comme tout MX, saut par saut : quand un tiers l’exploite, ce tiers est un sous-traitant au sens de votre loi sur la protection des données, avec un contrat et un lieu déclaré, exactement comme un relais sortant géré.
Le cas sans aucune entrée : un relais devant, et ce qu’il doit faire. Quand la box n’a aucune adresse publique d’aucune sorte, une machine qui en a une peut faire passer les connexions par un tunnel que la box ouvre vers elle (WireGuard, par exemple) : le monde frappe chez le relais, la box répond. Quatre règles, sans lesquelles le relais est un trou plutôt qu’une porte :
- Le relais est aveugle. Il route une connexion d’après le nom que le client demande (SNI) et ne termine jamais TLS : il transmet des octets qu’il ne peut pas lire. Un relais qui déchiffre lit votre courrier et vos fichiers ; pour un cabinet médical, c’est une révélation à un tiers.
- Le relais dit à la box qui appelle. À travers un relais TCP nu, chaque client arrive de l’adresse du relais : la box ne distingue plus un confrère d’un attaquant, un bannissement touche tout le monde, SPF et la DNSBL jugent le relais, et une route anonyme refuse une source qu’elle ne peut pas identifier. La réponse standard est le protocole PROXY (version 2), par lequel le relais préfixe chaque connexion avec l’adresse réelle du client. OxiMail ne lit pas le protocole PROXY aujourd’hui : tant qu’il ne le lit pas, un relais qui masque l’adresse du client n’est pas une entrée prise en charge pour les écouteurs du port 25, de la soumission, d’IMAP ou de HTTPS. Quand il le lira, il ne l’acceptera que de l’extrémité du tunnel.
- L’extrémité du tunnel n’est jamais dans
[security] trusted_ips. L’y inscrire donnerait à chaque client d’Internet l’exemption du limiteur de débit et du fail2ban. - Le port 25 ne traverse jamais un relais qui masque le client. SPF, la DNSBL et le greylisting lisent le pair : sans l’adresse du client, la box est un relais ouvert aux yeux des expéditeurs. Gardez le MX ailleurs (le MX de secours ci-dessus), et souvenez-vous que « pas de porte publique » n’est une phrase vraie que tant qu’aucun MX ne nomme la box.
Où vit le reste
- Verdicts et placement du courrier entrant : anti-spam.
- Le contrat DNS (SPF, DKIM, DMARC, MTA-STS, DANE) : authentification du courrier.
- Archiver des copies des flux : journalisation de conformité.
- Le filtrage utilisateur à la remise : règles.