Un réseau d'agences de voyage envoie sa newsletter professionnelle un mardi matin. Dans l'heure qui suit, le taux de désabonnement dépasse plusieurs pour cent, alors qu'il tourne d'habitude autour de quelques dixièmes. Les départs sont concentrés sur une poignée de domaines d'entreprises, et ils arrivent tous dans les minutes qui suivent l'envoi.
Personne n'a cliqué. Les passerelles de sécurité de ces entreprises ouvrent chaque lien du message pour l'analyser, y compris celui du pied de page libellé « Me désabonner en un clic ». Ce lien ouvrait une CloudPage qui désabonnait l'abonné dès son chargement, puis affichait une confirmation. Des clients se retrouvent désabonnés sans le savoir, et s'étonnent quelques semaines plus tard de ne plus rien recevoir.
La même semaine, une cliente conteste avoir accepté les offres partenaires. L'équipe ne peut montrer qu'une case cochée dans une data extension, réécrite à chaque passage sur la page. Pas de date d'origine, pas de trace du formulaire, pas de texte présenté à l'époque. Les deux incidents ont la même racine : une page de préférences conçue comme un écran d'administration, pas comme un point de collecte.
La documentation de Salesforce sur les ouvertures et les clics est explicite : les analyses antivirus parcourent le message et suivent les liens, et toute activité, même déclenchée par un système, est attribuée à l'abonné. Sur un envoi grand public, cela gonfle un taux de clic. Sur une page de désabonnement, cela produit des départs que personne n'a demandés, et le phénomène frappe surtout les adresses professionnelles.
La règle qui en découle tient en une phrase : une requête GET ne modifie jamais un statut ni une préférence. La page ouverte depuis l'email affiche un choix, et seul l'envoi du formulaire par un bouton, en POST, déclenche l'écriture. Les robots qui suivent les liens ne soumettent pas les formulaires.
GET : affichage des choix, création d'un jeton à usage unique
POST : contrôle du jeton, écriture des préférences, désabonnement
L'en-tête List-Unsubscribe échappe au problème. Salesforce l'ajoute aux envois commerciaux, il accepte le désabonnement en un clic décrit par la RFC 8058, et le client de messagerie envoie alors un POST que Salesforce traite lui-même. Vous ne pouvez ni le désactiver ni le modifier, et c'est une bonne nouvelle : des deux chemins de sortie proposés au destinataire, c'est le plus sûr.
⚠️ Le signal à surveiller : un pic de désabonnements concentré sur quelques domaines professionnels, dans les minutes qui suivent un envoi. Ce n'est pas un rejet de votre contenu, c'est une passerelle de sécurité qui fait son travail.
L'article 7 du RGPD pose deux exigences quand un traitement repose sur le consentement : le responsable du traitement doit être en mesure de démontrer que la personne l'a donné, et le retrait doit être aussi simple que le recueil. Pour la prospection par email, la CNIL rappelle que le consentement préalable est la règle vis-à-vis des particuliers, avec une exception pour les clients sollicités sur des produits ou services analogues, et demande un moyen simple de s'opposer dans chaque message ainsi qu'une liste d'opposition tenue à jour.
Ces textes sont publics, leur lecture appartient à votre délégué à la protection des données, et cet article ne remplace pas son avis. Ce qui nous revient, c'est de traduire l'exigence de démonstration en champs. Une case à true dans une table de préférences dit ce que le client a choisi en dernier. Elle ne dit ni quand, ni où, ni sur quel texte.
| Élément de preuve | Champ | Exemple |
|---|---|---|
| La personne | ClientId | 100245 |
| La finalité et le choix | Finalite, Valeur | partenaires, true |
| Le moment | DateEvenement | Date et heure du serveur, fuseau documenté |
| Le point de collecte | Source | centre_preferences, creation_compte, magasin |
| Le texte présenté | VersionTexte | PART-2026-03, qui renvoie à la table des libellés |
| Le contexte d'envoi | JobId | Identifiant de l'envoi qui a amené le client sur la page |
Cet historique s'écrit en insertion seule, avec une ligne par finalité à chaque soumission, même quand la valeur ne change pas. C'est la confirmation qui fait trace, pas seulement le changement. La table des préférences reste à côté, comme vue de l'état courant, pratique pour les requêtes de ciblage.
Le libellé affiché mérite le même soin. Lisez-le dans une table de textes versionnés à partir de la version en cours, et transmettez cette version au traitement du formulaire plutôt que de la coder en dur dans la page qui écrit. Le jour où le texte change, une version figée dans le traitement continue d'enregistrer l'ancienne référence, sans la moindre erreur, et la trace devient fausse.
💡 Le fuseau : Now() renvoie l'heure du serveur. Documentez ce fuseau dans la description de la data extension, ou convertissez la valeur avant de l'écrire. Une trace dont on ignore le fuseau perd beaucoup de son intérêt.
Un refus n'a pas la même portée selon la façon dont il est enregistré, et c'est la source d'un malentendu fréquent entre le libellé promis au client et l'effet obtenu.
| Mécanisme | Portée | Remarque |
|---|---|---|
| Préférence dans une data extension | Aucune tant que les envois ne la lisent pas | À appliquer par une exclusion ou une requête de ciblage |
| Désabonnement d'une liste de publication | Les envois associés à cette liste | C'est l'action du centre d'abonnement standard |
En-tête List-Unsubscribe | Comme le centre d'abonnement, au niveau de la liste | Traité par Salesforce, en POST, ni désactivable ni modifiable |
LogUnsubEvent sans contexte d'envoi | Désabonnement global de l'abonné | Visible dans All Subscribers |
LogUnsubEvent avec le contexte de l'envoi | La liste de l'envoi, l'événement est rattaché au job | Le désabonnement apparaît dans le suivi de l'envoi |
| Désabonnement de business unit | Une business unit d'un compte Enterprise 2.0 | Visible dans _BusinessUnitUnsubscribes depuis l'entreprise parente |
Une case libellée « ne plus recevoir aucun email commercial » engage la portée la plus large. Testez les deux variantes sur un compte de recette, avec et sans contexte d'envoi, et vérifiez ce que devient le statut dans All Subscribers avant de figer le libellé.
LogUnsubEvent est une requête Execute de l'API SOAP, appelée depuis l'AMPscript avec CreateObject et InvokeExecute. Elle a besoin d'au moins un identifiant d'abonné : SubscriberKey, SubscriberID ou EmailAddress. Si plusieurs sont fournis, ils doivent désigner le même abonné. Le contexte d'envoi passe par JobID, ListID et BatchID, et le JobID suffit puisque la plateforme retrouve les deux autres. Sans contexte exploitable, le désabonnement devient global. Le paramètre Reason est libre.
%%[
/* page de traitement, atteinte en POST, jeton déjà consommé */
SET @lue = CreateObject("ExecuteRequest")
SetObjectProperty(@lue, "Name", "LogUnsubEvent")
SET @prop = CreateObject("APIProperty")
SetObjectProperty(@prop, "Name", "SubscriberKey")
SetObjectProperty(@prop, "Value", @clientId)
AddObjectArrayItem(@lue, "Parameters", @prop)
SET @prop = CreateObject("APIProperty")
SetObjectProperty(@prop, "Name", "Reason")
SetObjectProperty(@prop, "Value", "Centre de preferences, refus total")
AddObjectArrayItem(@lue, "Parameters", @prop)
SET @code = InvokeExecute(@lue, @statut, @requestId)
]%%
Journalisez le résultat, sans quoi un échec passe inaperçu. La documentation retient le statut Event posted comme un succès, et considère les codes 12012 et 401 comme le signe d'un abonné déjà désabonné. Un test qui ignore ces deux codes remplit le journal de faux échecs, et un journal plein de faux échecs n'est plus lu par personne.
Une dernière précaution sur le contexte d'envoi. Les chaînes de personnalisation qui le portent devraient être renseignées sur une page ouverte depuis un lien CloudPagesURL, mais pas sur un envoi de test. Vérifiez leur contenu sur votre compte avant de vous appuyer dessus, parce qu'un contexte vide ne produit pas d'erreur, il change seulement la portée du désabonnement.
LogUnsubEvent ne sait que désabonner. Une réinscription, après un nouveau consentement explicite, passe par une mise à jour du statut de l'abonné et mérite une procédure à part, validée avec votre délégué à la protection des données.
Une préférence enregistrée ne sert à rien si les envois l'ignorent. Une requête quotidienne compare les refus totaux à ce que dit All Subscribers, et écrit dans une table de contrôle en mode Overwrite.
SELECT
p.ClientId,
s.Status,
p.DateMaj
FROM Preferences AS p
INNER JOIN _Subscribers AS s
ON s.SubscriberKey = p.ClientId
WHERE p.OptinNewsletter = 'false'
AND p.OptinPartenaires = 'false'
AND s.Status = 'active'
AND p.DateMaj < DATEADD(HOUR, -1, GETDATE())
Le délai d'une heure évite de signaler les refus en cours de traitement. La forme de la comparaison dépend du type réel de vos champs de préférence, alors testez-la dans une Query Activity avant de mettre la requête en production. Toute ligne qui reste est un refus non appliqué, qu'il s'agisse d'un appel en échec, d'une portée différente de celle attendue, ou d'un client qui a décoché ses cases sans demander de refus total. Ce dernier cas n'est pas une anomalie, mais vos requêtes de ciblage doivent l'exclure. Depuis l'entreprise parente, la même logique s'applique aux désabonnements par business unit.
Dans Adobe Campaign, le refus global correspond au champ de liste de blocage du destinataire, anciennement appelé liste noire, et les choix par finalité aux abonnements à des services d'information, dont la plateforme conserve l'historique.
Marketing Cloud couvre le refus global avec le statut dans All Subscribers et les listes de publication. L'historique des consentements par finalité, lui, n'existe pas en standard : c'est à vous de le construire dans une data extension, avec la date, la source et la version du texte. C'est la principale charge de conception d'un centre de préférences sur mesure, et celle que l'on découvre le jour où il faut répondre à une réclamation.
Avant d'ouvrir le code de votre centre de préférences, posez une seule question : que se passe-t-il si un robot charge cette page sans rien cliquer ? Si la réponse contient une écriture, vous avez un incident en attente, et une recette humaine ne le reproduira jamais. Le reste, la trace datée, les versions de texte, la portée du désabonnement, se construit ensuite.
Les passerelles de sécurité de certaines entreprises ouvrent chaque lien du message pour l'analyser, y compris le lien de désabonnement du pied de page. La documentation de Salesforce précise que toute activité, même déclenchée par un système, est attribuée à l'abonné. Si la page de désabonnement écrit dès son chargement, ces analyses produisent des départs que personne n'a demandés. Le signal typique est un pic concentré sur quelques domaines professionnels, dans les minutes qui suivent l'envoi.
La règle est qu'une requête GET ne modifie jamais un statut ni une préférence. La page ouverte depuis l'email se contente d'afficher un choix et de créer un jeton à usage unique, et seul l'envoi du formulaire par un bouton, en POST, déclenche le contrôle du jeton puis l'écriture des préférences. Les robots qui suivent les liens ne soumettent pas les formulaires.
Une case à true dans une table de préférences dit ce que le client a choisi en dernier, mais ni quand, ni où, ni sur quel texte. L'article traduit l'exigence de démonstration en champs : la personne (ClientId), la finalité et le choix, le moment (DateEvenement), le point de collecte (Source), le texte présenté (VersionTexte) et le contexte d'envoi (JobId). Cet historique s'écrit en insertion seule, avec une ligne par finalité à chaque soumission, même quand la valeur ne change pas, la table des préférences restant à côté comme vue de l'état courant.
La documentation retient le statut Event posted comme un succès, et considère les codes 12012 et 401 comme le signe d'un abonné déjà désabonné. Il faut donc journaliser le résultat de l'appel, sans quoi un échec passe inaperçu. Un test qui ignore ces deux codes remplit le journal de faux échecs, et un journal plein de faux échecs n'est plus lu par personne.
Non : une préférence dans une data extension n'a aucune portée tant que les envois ne la lisent pas, elle doit être appliquée par une exclusion ou une requête de ciblage. Le désabonnement d'une liste de publication porte sur les envois associés à cette liste, et l'en-tête List-Unsubscribe agit au même niveau. Pour vérifier que le refus est bien appliqué, une requête quotidienne compare les refus totaux à ce que dit All Subscribers et écrit dans une table de contrôle en mode Overwrite.
Je peux relire vos pages de préférences, vos appels de désabonnement et vos traces de consentement, et vous rendre un diagnostic écrit avec les correctifs à appliquer.
Me décrire votre contexte →