# Mails rejetés par Outlook et Hotmail : le DMARC que les applications oublient

> Pendant quatre jours de novembre 2026, Hotmail et Outlook.com ont refusé une partie des mails venus de domaines sans DMARC. Microsoft parle d'une erreur, retirée le 9 novembre. Les liens de connexion de nos applications passent pourtant par le même guichet : un petit outil Go, testé, pour lire le SPF, le DMARC et les clés DKIM d'un domaine.


*Ou : Comment une règle que personne n'avait écrite a trouvé tous les domaines qu'elle aurait visés*

Le 5 novembre 2026, des administrateurs ont vu revenir des mails envoyés à des adresses Hotmail, Outlook.com ou Live, avec un rebond inhabituel : il manquerait au message ses enregistrements d'authentification SPF, DKIM ou DMARC. Pendant quatre jours, les messageries grand public de Microsoft ont refusé une partie du courrier de petites structures, sans prévenir. D'après les premiers témoignages, les domaines refusés avaient souvent un point commun, l'absence d'enregistrement DMARC, et un simple `p=none` publié dans le DNS suffisait parfois à faire repasser le courrier. Le 10 novembre, Microsoft a donné son explication aux administrateurs de ses clients : une mise à jour de ses règles antispam marquait et bloquait des mails à tort. Elle a été retirée, et l'incident a pris fin le 9 novembre à 12 h 02, heure de Paris[^1].

La règle n'existait donc pas. Le trou qu'elle a trouvé, si. Et ce trou ne concerne pas que les administrateurs de messagerie : il est dans chaque application qui envoie un lien de connexion, une facture ou une réinitialisation de mot de passe au nom du domaine d'un client. J'ai consacré une série entière à [la connexion par lien magique](/blog/magic-link-connexion-sans-mot-de-passe/), et tout ce qu'elle construit tient à une condition : que le mail arrive. Voici de quoi lire, en Go et sans dépendance, ce qu'un destinataire consulte dans le DNS avant de l'accepter.

## Mails refusés sans DMARC : vérifier SPF, DKIM et DMARC d'un domaine avec Go

### Quatre jours de rebonds pour une règle qui n'existait pas

Le rebond tenait en une ligne, rapportée par plusieurs utilisateurs sur le forum d'entraide de Microsoft[^2] :

```text
550 5.7.1 Message rejected: Message lacks SPF/DKIM/DMARC Auth Records (AS9031519)
```

Le texte accuse l'expéditeur, et le code 5.7.1 est un refus générique. D'autres ont reçu un `550 5.7.23`, qui parle d'une violation SPF. Microsoft, de son côté, n'a rien publié pendant plusieurs jours, sinon dans le tableau de bord de santé de Microsoft 365, que seuls les administrateurs de ses clients consultent. La chronologie, en heure de Paris :

| Date | Événement |
|---|---|
| 2 avril 2025 | Microsoft annonce ses exigences pour les domaines qui envoient plus de 5 000 mails par jour vers Outlook.com, Hotmail et Live : SPF, DKIM et DMARC au moins `p=none`, aligné |
| 29 avril 2025 | Mise à jour de l'annonce : les mails non conformes seront refusés avec le code `550 5.7.515`, plutôt que rangés dans les indésirables |
| 5 mai 2025 | Entrée en vigueur de cette règle |
| 5 novembre 2026 | Premiers rebonds signalés, y compris chez de petits expéditeurs |
| 6 novembre 2026, 19 h | Début de l'incident, selon Microsoft |
| 9 novembre 2026, 12 h 02 | Fin de l'incident : la mise à jour fautive est retirée |
| 9 novembre 2026, 16 h 42 | Numerama publie la marche à suivre pour remédier au blocage[^3] |
| 10 novembre 2026, 6 h 36 | Microsoft publie l'état final de l'incident EX1545193 |

Numerama a donc expliqué comment contourner le blocage un peu plus de quatre heures et demie après sa fin, et personne n'y est pour rien : à ce moment, Microsoft n'avait encore rien dit en public. Le décalage dit surtout une chose. Pendant quatre jours, le seul diagnostic disponible était le texte du rebond, et ce texte désignait le DMARC.

La règle des 5 000 mails par jour, elle, existe bien depuis mai 2025[^4], et sa page d'aide est toujours en ligne au 11 novembre 2026[^5]. Gmail et Yahoo imposent des conditions voisines depuis février 2024 : SPF ou DKIM pour tout le monde, les trois, alignés, pour les gros expéditeurs[^6]. La fausse règle de novembre ressemblait donc beaucoup à la vraie, appliquée à tout le monde. C'est ce qui la rendait crédible, et c'est pourquoi je ne parierais pas contre une vraie, un jour.

### Trois enregistrements, une seule adresse

Un mail porte deux adresses d'expéditeur. Celle de l'enveloppe, transmise par le protocole SMTP (`MAIL FROM`), reçoit les rebonds et personne ne la voit. Celle de l'en-tête `From:`, que le destinataire lit dans sa messagerie, est celle qu'on usurpe. Les trois enregistrements se partagent le contrôle :

- **SPF** publie la liste des serveurs autorisés à envoyer pour le domaine de l'enveloppe.
- **DKIM** signe le message ; la clé publique qui permet de vérifier la signature est publiée dans le DNS du domaine signataire.
- **DMARC** demande au destinataire de vérifier qu'au moins l'un des deux contrôles réussis porte sur le domaine affiché dans `From:`, dit quoi faire sinon (`none`, `quarantine` ou `reject`) et où envoyer les rapports.

Dans une salle de tri postale, SPF vérifie que la lettre a été déposée à un guichet agréé, sans regarder le nom imprimé au dos. DKIM vérifie que le sceau de cire est intact, sans demander à qui appartient le sceau. DMARC est le seul employé qui lit le nom de l'expéditeur sur l'enveloppe et demande si le guichet ou le sceau sont bien les siens. C'est ce qu'on appelle l'alignement.

Et c'est là que les applications se prennent les pieds dans le tapis. Elles envoient le plus souvent par un service tiers. L'enveloppe porte le domaine du service, pour qu'il recueille les rebonds : SPF passe, pour lui. Le service signe avec son propre domaine : DKIM passe, pour lui. L'en-tête annonce `noreply@client.example` : DMARC échoue, faute d'alignement. Tout est vert, sauf ce qui compte. La correction se règle chez le service d'envoi : les services transactionnels proposent en général de signer avec le domaine du client et d'utiliser un domaine de rebond placé sous le sien, à condition de publier les enregistrements qu'ils fournissent.

![Illustration de trois tampons de laiton gravés d'une clé, d'une plume et d'une balance, debout sur un bureau sombre devant une enveloppe ambrée fermée d'un sceau de cire](/images/outlook-dmarc-mails-refuses-verifier-domaine-go-trois-sceaux.original.webp "SPF dit qui peut envoyer, DKIM signe, DMARC vérifie que les deux parlent bien du domaine affiché.")

### Le lien magique passe par le même guichet

Ce n'est pas un sujet d'administrateur mail. Le formulaire de contact, le lien de connexion, la facture mensuelle et le mot de passe oublié sont écrits par des développeurs, déployés par des développeurs, et partent souvent avec le domaine du client par un service que le client n'a jamais configuré. Quand j'ai écrit [la route HTTP qui envoie le lien magique](/blog/endpoint-magic-link-go-anti-enumeration-rate-limiting/), je me suis inquiété des abus, d'un attaquant qui transformerait le formulaire en canon à mails. Le [comparatif avec les passkeys et les codes à usage unique](/blog/magic-link-vs-passkeys-otp-oauth-comparatif/) relevait déjà que le lien magique est la seule de ces méthodes qui dépend d'un filtre antispam que personne ne contrôle. Novembre a montré à quoi ressemble ce filtre quand il se trompe.

**DevOps Dave :** Le formulaire de contact du client est en production depuis lundi. Aucune erreur dans les journaux.

**Security Sarah :** Et dans la boîte de réception du client ?

**DevOps Dave :** Le service d'envoi répond 202 à chaque appel. Tout est parti.

**Security Sarah :** Parti, oui. Le service a accepté ton mail. Hotmail l'a refusé deux minutes plus tard, et le rebond dort dans un tableau de bord que personne n'ouvre.

**DevOps Dave :** Le client n'a reçu aucune plainte.

**Security Sarah :** Ceux qui pourraient se plaindre attendent toujours leur lien de connexion.

### SPF : compter les requêtes avant le destinataire

Le premier contrôle de l'outil, que j'ai appelé `verifmail`, porte sur la limite la plus traître de SPF. La RFC 7208 plafonne à 10 le nombre de termes qui déclenchent une requête DNS pendant une évaluation : les mécanismes `include`, `a`, `mx`, `ptr` et `exists`, et le modificateur `redirect`. Au-delà, le résultat est une erreur permanente[^7]. `ip4`, `ip6`, `all` et `exp` ne comptent pas.

La limite est traître parce qu'elle se cache dans les `include`. Chaque service ajouté au domaine, outil de facturation, lettre d'information, support client, envoi transactionnel, apporte son `include`, et chacun peut en contenir d'autres. À la onzième requête, ce n'est pas le dernier service qui échoue : c'est l'enregistrement entier, pour tous les envois. Et un destinataire s'arrête au premier mécanisme qui correspond à l'adresse de l'expéditeur, mais une adresse qui ne correspond à rien, celle d'un service oublié ou d'un usurpateur, l'oblige à tout évaluer. C'est ce pire cas que l'outil compte :

```go
// declencheDNS dit si le terme compte dans la limite des 10 requêtes.
// all, ip4, ip6 et exp n'y comptent pas.
func (t terme) declencheDNS() bool {
	if t.Modif {
		return t.Nom == "redirect"
	}
	switch t.Nom {
	case "include", "a", "mx", "ptr", "exists":
		return true
	}
	return false
}
```

Le parcours suit les `include` et les `redirect` récursivement, en gardant le chemin parcouru pour repérer les boucles :

```go
	for _, t := range termes {
		// redirect est ignoré quand l'enregistrement a un « all » (RFC 7208, 6.1).
		if !t.declencheDNS() || (t.Nom == "redirect" && aUnAll) {
			continue
		}
		b.Requetes = append(b.Requetes, requete{Terme: t.String(), Niveau: niveau})
		// ... garde-fou : analyse abandonnée au-delà de 50 requêtes
		if t.Nom != "include" && t.Nom != "redirect" {
			continue // a, mx, ptr, exists : une requête, rien à suivre
		}
		cible := strings.ToLower(strings.TrimSuffix(t.Valeur, "."))
		if strings.Contains(cible, "%") {
			b.Notes = append(b.Notes, "macro non suivie : "+t.String())
			continue
		}
		if chemin[cible] {
			return fmt.Errorf("boucle SPF : %s s'inclut lui-même", cible)
		}
		chemin[cible] = true
		redir := t.Nom == "redirect" && (niveau == 0 || viaRedirect)
		if err := v.compterSPF(ctx, cible, niveau+1, redir, b, chemin); err != nil {
			return err
		}
		delete(chemin, cible)
	}
```

Un `include` vers un domaine qui n'a pas de SPF est une erreur permanente, pas un résultat neutre, et l'outil le traite comme tel. Deux enregistrements `v=spf1` sur le même nom aussi. Trois choses restent hors de portée : les macros comme `%{i}`, qui dépendent du message et que l'outil compte sans les suivre ; la limite de 10 adresses par mécanisme `mx` ; et les requêtes qui ne renvoient rien, que la RFC conseille de limiter à deux.

![Illustration d'un réseau de tubes de laiton ramifiés sur un mur sombre, chaque branche terminée par une ampoule ambrée, dont la dernière vanne éclate dans un nuage de vapeur](/images/outlook-dmarc-mails-refuses-verifier-domaine-go-dix-requetes.original.webp "Chaque include peut en cacher d'autres : à la onzième requête, tout l'enregistrement SPF tombe en erreur.")

### DMARC : la balise v d'abord, puis la remontée de l'arbre

DMARC a changé de texte de référence en mai 2026. La RFC 9989 remplace la RFC 7489 de mars 2015[^8], et deux changements comptent pour un outil comme celui-ci.

Le premier tient à la découverte de la politique. Quand un sous-domaine ne publie rien, il faut trouver le domaine de l'organisation, dont l'enregistrement s'applique. L'ancien texte s'en remettait à la liste des suffixes publics, celle qui sait que `gouv.fr` ou `co.uk` ne sont à personne. Le nouveau remonte l'arbre du DNS : il interroge `_dmarc.` devant chaque parent, huit requêtes au plus, et retient l'enregistrement trouvé le plus haut, sauf si une balise `psd` en décide autrement. `psd=n` déclare le domaine de l'organisation, `psd=y` un suffixe public, auquel cas l'organisation est le nom juste en dessous.

```go
// nomsARemonter liste les noms à interroger après le domaine lui-même,
// selon la « DNS Tree Walk » de la RFC 9989 (section 4.10) : au plus
// sept noms, jusqu'au domaine de premier niveau compris.
func nomsARemonter(domaine string) []string {
	labels := strings.Split(domaine, ".")
	if len(labels) > 8 {
		labels = labels[len(labels)-7:]
	} else {
		labels = labels[1:]
	}
	var noms []string
	for ; len(labels) > 0; labels = labels[1:] {
		noms = append(noms, strings.Join(labels, "."))
	}
	return noms
}
```

La boucle qui s'en sert garde chaque enregistrement trouvé et s'arrête sur la première balise `psd` :

```go
	for i, nom := range noms {
		d, ok, err := v.lire(ctx, nom, &r)
		if err != nil {
			return r, err
		}
		if !ok {
			continue
		}
		trouves = append(trouves, trouve{nom, d})
		switch d.Balises["psd"] {
		case "n": // domaine organisationnel déclaré
			return retenir(nom, d)
		case "y": // suffixe public : l'organisation est le nom juste en dessous
			dessous := domaine
			if i > 0 {
				dessous = noms[i-1]
			}
			for _, t := range trouves {
				if t.nom == dessous {
					return retenir(t.nom, t.d)
				}
			}
			return retenir(nom, d)
		}
	}
	if len(trouves) == 0 {
		return r, nil
	}
	// Sinon, l'enregistrement trouvé le plus haut, celui qui a le moins de labels.
	t := trouves[len(trouves)-1]
	return retenir(t.nom, t.d)
```

Le second changement supprime la balise `pct`, qui appliquait la politique à un pourcentage des mails. Elle n'était appliquée correctement qu'à 0 ou à 100, et `t=y`, un mode test, la remplace. L'outil la lit toujours, puisqu'on la croise encore souvent, et signale qu'elle a disparu.

L'analyse de l'enregistrement, elle, commence par une règle stricte : la balise `v` doit venir en premier et valoir exactement `DMARC1`, en majuscules. Un enregistrement qui commence par `p=reject; v=DMARC1` n'existe pas pour le destinataire. Deux enregistrements sous le même nom non plus, puisque la RFC demande de les ignorer tous les deux.

```go
	for _, morceau := range strings.Split(enr, ";") {
		morceau = strings.TrimSpace(morceau)
		if morceau == "" {
			continue
		}
		nom, val, ok := strings.Cut(morceau, "=")
		nom, val = strings.TrimSpace(nom), strings.TrimSpace(val)
		if premier {
			if nom != "v" || val != "DMARC1" {
				return d, errPasDMARC
			}
			premier = false
			continue
		}
		// ... lecture et validation de p, sp, np, adkim, aspf, t, psd, rua
	}
```

Sans `p` valide, l'enregistrement vaut `p=none` s'il a une adresse `rua`, et rien du tout sinon.

### DKIM : le sélecteur ne se devine pas

Une clé DKIM se trouve sous `<sélecteur>._domainkey.<domaine>`[^9]. Le sélecteur est un nom libre, choisi par l'expéditeur pour faire tourner ses clés, et le destinataire le lit dans la signature du mail reçu, à la balise `s=`. Le DNS, lui, ne répond qu'aux noms qu'on lui demande : sans un mail sous la main, il faut connaître le sélecteur. Microsoft 365 utilise `selector1` et `selector2` pour tous ses clients[^10] ; les services d'envoi indiquent le leur au moment de la configuration. D'où l'option `-dkim`, qui prend une liste de sélecteurs à essayer.

```go
	p = strings.Join(strings.Fields(p), "")
	if p == "" {
		return "clé révoquée (p= vide)", nil // RFC 6376, section 3.6.1
	}
	brut, err := base64.StdEncoding.DecodeString(p)
	if err != nil {
		return "", fmt.Errorf("clé illisible : %w", err)
	}
	switch k := balises["k"]; k {
	case "", "rsa":
		if pub, err := x509.ParsePKIXPublicKey(brut); err == nil {
			if r, ok := pub.(*rsa.PublicKey); ok {
				return fmt.Sprintf("clé RSA de %d bits", r.N.BitLen()), nil
			}
		}
		// ... repli sur PKCS #1, puis le cas ed25519
```

Une clé vide n'est pas une clé absente : c'est une clé révoquée, publiée exprès pour que les anciennes signatures échouent. `net.LookupTXT` recolle les morceaux d'un enregistrement TXT découpé en chaînes de 255 octets, ce que DKIM exige[^11] ; une clé RSA de 2 048 bits ne tient pas en une seule.

### Les tests de table, et une panne qui n'est pas une absence

Les analyseurs se testent sans réseau. Le résolveur est une interface d'une méthode, que `*net.Resolver` fournit et qu'une table remplace dans les tests :

```go
// fauxDNS remplace le résolveur : un nom absent répond « not found ».
type fauxDNS map[string][]string

func (f fauxDNS) LookupTXT(_ context.Context, nom string) ([]string, error) {
	if enrs, ok := f[nom]; ok {
		return enrs, nil
	}
	return nil, &net.DNSError{Err: "no such host", Name: nom, IsNotFound: true}
}
```

Le cas le plus délicat, la remontée de l'arbre, tient dans une table, à la manière des [tests de table en Go](/blog/table-driven-tests-go/) :

```go
		{"direct.example", true, "direct.example", "reject"},
		{"newsletter.org.example", true, "org.example", "quarantine"},               // sp du parent
		{"x.mail.org.example", true, "org.example", "quarantine"},                   // le moins de labels l'emporte
		{"y.equipe.psdn.example", true, "equipe.psdn.example", "none"},              // psd=n arrête la remontée
		{"mail.geante.banque.example", true, "geante.banque.example", "quarantine"}, // sous le suffixe public
		{"autre.banque.example", true, "banque.example", "none"},                    // politique du suffixe public, sp=none
		{"double.example", false, "", ""},                                           // deux enregistrements : aucun
```

Les cinq fonctions de test totalisent 47 cas, dont une liste de onze `include` pour franchir la limite, une boucle entre deux domaines et l'exemple à treize labels de la RFC 9989, qui doit donner ses sept noms parents, huit requêtes en comptant la première :

```text
$ go test -count=1 ./...
ok  	verifmail	0.004s
```

Le réseau réel a ajouté un cas que les tests ne voyaient pas. Lors de mes premiers essais, plusieurs requêtes ont expiré, et l'outil a écrit « erreur » là où un outil plus naïf aurait écrit « aucun enregistrement ». La différence compte : la RFC 9989 interdit d'appliquer DMARC quand le DNS dit qu'il n'y a rien, mais laisse le destinataire libre de différer un mail quand le DNS ne répond pas. La fonction qui interroge le DNS sépare donc les deux cas, retente une requête expirée deux fois, et l'option `-tcp` contourne les réponses trop grosses pour UDP, fréquentes sur les domaines qui empilent les enregistrements de vérification.

### Ce que publient Microsoft, Google et l'Union européenne

Les sorties suivantes datent du 11 novembre 2026, entre 2 h 29 et 2 h 31, heure de Paris, avec Go 1.27.3 sous Linux. D'abord les deux grandes messageries grand public :

```text
$ ./verifmail -tcp -dkim selector1,selector2 outlook.com
outlook.com
  SPF    v=spf1 include:spf2.outlook.com -all
         include:spf2.outlook.com
         1 requête DNS sur 10 (dans la limite), terme final -all
  DMARC  _dmarc.outlook.com : v=DMARC1; p=none; sp=quarantine; pct=100; rua=mailto:rua@dmarc.microsoft; ruf=mailto:ruf@dmarc.microsoft; fo=1
         politique none (domaine), alignement DKIM r, SPF r
         ! p=none : surveillance seule, aucun mail refusé
         ! pct a disparu de la RFC 9989, t=y le remplace
  DKIM   selector1 : clé RSA de 2048 bits
         selector2 : clé RSA de 2048 bits

$ ./verifmail -tcp -dkim 20230601,20210112 gmail.com
gmail.com
  SPF    v=spf1 redirect=_spf.google.com
         redirect=_spf.google.com
         1 requête DNS sur 10 (dans la limite), terme final ~all
  DMARC  _dmarc.gmail.com : v=DMARC1; p=none; sp=quarantine; rua=mailto:mailauth-reports@google.com
         politique none (domaine), alignement DKIM r, SPF r
         ! p=none : surveillance seule, aucun mail refusé
  DKIM   20230601 : clé révoquée (p= vide)
         20210112 : clé révoquée (p= vide)
```

Outlook.com publie `p=none`, le minimum exact que Microsoft exige des gros expéditeurs, et Gmail fait de même. Ce n'est pas de la négligence : la RFC 9989 déconseille `p=reject` aux domaines dont les utilisateurs écrivent à des listes de diffusion, qui réexpédient les mails et cassent l'alignement[^12]. Les deux serrent en revanche leurs sous-domaines (`sp=quarantine`). Les deux sélecteurs essayés chez Gmail répondent par une clé révoquée : quelqu'un a publié ces noms, puis vidé la clé. Celui qui signe aujourd'hui se lit dans l'en-tête d'un mail reçu de Gmail, pas dans le DNS.

Puis trois domaines publics, choisis pour leurs différences :

```text
$ ./verifmail -tcp europa.eu
europa.eu
  SPF    v=spf1 -all
         0 requête DNS sur 10 (dans la limite), terme final -all
  DMARC  aucun enregistrement (_dmarc.europa.eu, _dmarc.eu)

$ ./verifmail -tcp ec.europa.eu
ec.europa.eu
  SPF    v=spf1 include:_spf.tech.ec.europa.eu include:_spf-jrc.tech.ec.europa.eu -all
         include:_spf.tech.ec.europa.eu
           include:spf-00244802.pphosted.com
           include:spf.protection.outlook.com
         include:_spf-jrc.tech.ec.europa.eu
         4 requêtes DNS sur 10 (dans la limite), terme final -all
  DMARC  _dmarc.ec.europa.eu : v=DMARC1; p=reject; sp=none; np=quarantine; psd=n; rua=mailto:swtyii6t@ag.eu.dmarcadvisor.com; adkim=s; aspf=s
         politique reject (domaine), alignement DKIM s, SPF s

$ ./verifmail -tcp education.gouv.fr
education.gouv.fr
  SPF    v=spf1 redirect=_spf.adc.education.fr
         redirect=_spf.adc.education.fr
           include:_spfuser.adc.education.fr
           include:_spfappli.adc.education.fr
           include:_spfext.adc.education.fr
         4 requêtes DNS sur 10 (dans la limite), terme final ~all
  DMARC  _dmarc.gouv.fr : v=DMARC1; p=reject; psd=y; sp=none; rua=mailto:ivd04g8f@ag.eu.dmarcadvisor.com
         politique none (sous-domaine de gouv.fr), alignement DKIM r, SPF r
```

Le domaine du site officiel de l'Union européenne, `europa.eu`, déclare par son `-all` qu'il n'envoie aucun mail, et ne publie aucun DMARC, pas plus que `.eu` au-dessus de lui. SPF protège l'enveloppe ; l'adresse affichée dans `From:`, c'est DMARC qui la protège, et rien, dans le DNS, ne dit au destinataire quoi faire d'un message qui prétend venir de `europa.eu`. L'outil sort en code 1, comme pour tout domaine sans DMARC, de quoi le placer dans une intégration continue. Un étage plus bas, le domaine de la Commission européenne publie l'enregistrement le plus strict de la série : `p=reject`, alignement strict des deux côtés, et les balises `np` et `psd` que la RFC 9989 a rendues officielles.

Le domaine du ministère français de l'Éducation nationale, `education.gouv.fr`, ne publie pas d'enregistrement. La remontée trouve `gouv.fr`, qui se déclare suffixe public (`psd=y`) et donne `sp=none` à ce qui se trouve en dessous. Selon la RFC 9989, la politique de `education.gouv.fr` est donc `none`. Selon la RFC 7489, `gouv.fr` figurant dans la liste des suffixes publics[^13], `education.gouv.fr` était son propre domaine organisationnel, sans enregistrement : pas de DMARC du tout. Le même domaine a une politique ou n'en a pas selon le texte que lit le destinataire. Reste à savoir lesquels ont déjà changé de lecture.

### Ce que le DNS ne dit pas

L'outil lit ce qui est publié, pas ce qui est envoyé. L'alignement dépend du domaine réellement porté par l'enveloppe et de la balise `d=` de la vraie signature, et seul un mail envoyé les montre. Le test qui tranche coûte deux minutes : envoyer depuis l'application un mail vers une adresse Outlook.com et une adresse Gmail, ouvrir les en-têtes, et lire `Authentication-Results`. Le cas de l'application qui passe par un service tiers mal configuré ressemble à ceci, en simplifié :

```text
spf=pass smtp.mailfrom=rebonds.service-envoi.example
dkim=pass header.d=service-envoi.example
dmarc=fail action=none header.from=client.example
```

Microsoft documente une valeur de plus, `dmarc=bestguesspass` : le domaine n'a pas de DMARC, mais il l'aurait passé s'il en avait un[^14]. C'est l'équivalent administratif de « vous auriez été admis, si vous vous étiez inscrit ». L'outil ne vérifie pas non plus le DNS inverse des serveurs, le taux de plaintes que surveillent Gmail et Yahoo, ni le lien de désinscription en un clic exigé des envois en masse.

### Publier p=none, lire, puis serrer

La marche à suivre prudente est celle que décrit la RFC 9989 elle-même. D'abord, publier un enregistrement de surveillance avec une adresse pour les rapports :

```text
_dmarc.client.example.  TXT  "v=DMARC1; p=none; rua=mailto:dmarc@client.example"
```

Ensuite, lire les rapports agrégés, des fichiers XML que les destinataires envoient périodiquement et que la RFC conseille de confier à un programme plutôt qu'à des yeux humains. Ils listent chaque source qui a utilisé le domaine, alignée ou non. Chaque service non aligné se corrige à la source : signature DKIM avec le domaine du client, domaine de rebond sous le sien. La RFC parle de plusieurs mois pour un domaine qui envoie rarement, le temps que chaque flux se montre au moins une fois.

Enfin, passer à `p=quarantine`, puis à `p=reject`. Pour un domaine dont les humains écrivent à des listes de diffusion, la RFC conseille de rester au moins un mois en `none`, puis aussi longtemps en `quarantine`, avant d'envisager `reject`[^12]. Pour une application, un sous-domaine réservé à ses envois, du genre `notifications.client.example`, porte sa propre politique : avec la remontée de l'arbre, un enregistrement publié sur le sous-domaine s'applique avant celui du parent, et on peut le serrer sans toucher aux boîtes des humains.

Microsoft a fait, sans le vouloir, l'exercice d'évacuation que personne n'avait prévu. L'alarme était fausse, et elle a sonné dans les bons couloirs : ceux où le domaine du client n'avait jamais publié de DMARC, et où l'application envoyait quand même des liens de connexion en son nom. La prochaine alarme sera peut-être vraie. Un `p=none` coûte une ligne de DNS. Le lien magique qui n'arrive pas coûte un utilisateur, qui ne saura jamais pourquoi il n'a pas pu se connecter.

---

[^1]: L'incident EX1545193 du tableau de bord de santé de Microsoft 365, réservé aux administrateurs, est reproduit sur [la page des incidents Microsoft 365 de l'université de Pennsylvanie](https://mailservices.isc.upenn.edu/computing/email/penno365/alerts/ms-incidents.html) : début le 6 novembre 2026 à 18 h UTC, fin le 9 novembre à 11 h 02 UTC, état final publié le 10 novembre à 5 h 36 UTC. La cause donnée est une mise à jour récente des règles antispam, entièrement retirée. Le même texte cite les rebonds 550 5.7.1 et 550 5.7.23 et développe le sigle SPF en « Service Provider Foundation », au lieu de Sender Policy Framework ; l'incident a été corrigé quand même. MailRoute, qui a vu les premiers rebonds dans ses propres journaux le 5 novembre, a [mis son article à jour le 10 novembre](https://mailroute.net/blog/outlook-rejecting-email-without-dmarc) avec la même heure de fin.

[^2]: Un [fil du forum Microsoft Q&A sur les mails bloqués faute de SPF, DKIM et DMARC](https://learn.microsoft.com/en-us/answers/questions/6063076/incoming-emails-being-blocked-due-to-lack-of-spf-d), ouvert le 6 novembre 2026. Un participant y rapporte que son message de test est passé après la publication d'un enregistrement `v=DMARC1; p=none`, et d'autres y conseillent le même contournement.

[^3]: [L'article de Numerama](https://www.numerama.com/cyberguerre/2370747-sans-prevenir-microsoft-sest-mis-a-bloquer-des-mails-de-petites-structures-vers-hotmail-et-outlook-voici-comment-y-remedier.html) du 9 novembre 2026, qui situe le début des rejets au 5 novembre et note qu'aucune annonce officielle ne les accompagnait.

[^4]: [L'annonce de Microsoft du 2 avril 2025](https://techcommunity.microsoft.com/blog/microsoftthreatprotectionblog/strengthening-email-ecosystem-outlook%E2%80%99s-new-requirements-for-high%E2%80%90volume-senders/4399730) sur les exigences pour les expéditeurs de plus de 5 000 mails par jour, mise à jour le 29 avril 2025 pour remplacer le dossier des indésirables par un refus, à partir du 5 mai.

[^5]: La [page d'aide de Microsoft sur l'erreur 550 5.7.515](https://support.microsoft.com/en-us/outlook/fix-ndr-error-550-5-7-515-in-outlook-com), consultée le 11 novembre 2026 : le seuil de 5 000 messages s'apprécie sur le domaine de l'en-tête `From:`, et un enregistrement `v=DMARC1; p=none` y sert d'exemple.

[^6]: Les [exigences de Google envers les expéditeurs](https://support.google.com/mail/answer/81126), en vigueur depuis le 1er février 2024, et les [bonnes pratiques de Yahoo](https://senders.yahooinc.com/best-practices/), appliquées progressivement à partir de février 2024 : SPF ou DKIM pour tous, SPF, DKIM et DMARC au moins `p=none` avec alignement pour ceux qui envoient en masse.

[^7]: La [section 4.6.4 de la RFC 7208](https://www.rfc-editor.org/rfc/rfc7208.html#section-4.6.4), qui fixe aussi la limite de 10 adresses par mécanisme `mx` et conseille de limiter à deux les requêtes qui ne trouvent rien. La section 5.2 précise qu'un `include` vers un domaine sans SPF produit une erreur permanente, et la section 6.1 qu'un `redirect` est ignoré si l'enregistrement contient `all`.

[^8]: La [RFC 9989](https://www.rfc-editor.org/rfc/rfc9989.html), publiée en mai 2026, rend obsolètes les RFC 7489 et 9091. La remontée de l'arbre est décrite en section 4.10, la suppression de `pct` en annexe A.6, et la règle de la balise `v` en section 4.7.

[^9]: La [section 3.6.2.1 de la RFC 6376](https://www.rfc-editor.org/rfc/rfc6376.html#section-3.6.2.1), qui définit DKIM. La section 3.6.1 précise qu'une balise `p=` vide signifie une clé révoquée.

[^10]: La [documentation de Microsoft sur DKIM pour les domaines personnalisés](https://learn.microsoft.com/en-us/defender-office-365/email-authentication-dkim-configure) : les noms `selector1._domainkey` et `selector2._domainkey` sont les mêmes pour toutes les organisations Microsoft 365.

[^11]: La [documentation de `Resolver.LookupTXT`](https://pkg.go.dev/net#Resolver.LookupTXT) dans la bibliothèque standard de Go : les chaînes d'un même enregistrement TXT sont concaténées. La section 3.6.2.2 de la RFC 6376 l'exige pour les clés DKIM.

[^12]: La [section 7.4 de la RFC 9989](https://www.rfc-editor.org/rfc/rfc9989.html#section-7.4), sur les listes de diffusion et `p=reject`, et la [section 5.1](https://www.rfc-editor.org/rfc/rfc9989.html#section-5.1), qui décrit le passage de la surveillance à l'application de la politique.

[^13]: La [liste des suffixes publics](https://publicsuffix.org/list/public_suffix_list.dat), où `gouv.fr` figure dans la section consacrée à `.fr`.

[^14]: La [documentation des en-têtes antispam de Microsoft](https://learn.microsoft.com/en-us/defender-office-365/message-headers-eop-mdo), qui décrit la syntaxe de `Authentication-Results` et la valeur `bestguesspass`.



