Le crème vivait sur `<body>`, que Gmail supprime en réécrivant le message : la carte blanche se retrouvait collée au fond du client, blanc sur blanc chez les uns, bloc éblouissant sur fond noir chez les autres. Elle n'a plus de fond du tout — autour d'elle passe celui du lecteur, et c'est la bordure qui délimite, ce qui reste juste sur n'importe quel fond, y compris ceux qu'on ne connaît pas. Le mode sombre est déclaré (`color-scheme`) plutôt que subi : sans cette déclaration, Gmail et Apple inversent au jugé et le vert de marque en ressort délavé. Un piège qui ne se voyait qu'à l'écran : la marge intérieure était posée sur les `<table>`. En modèle de bordures fusionnées — celui de tout mail, et celui qu'Outlook applique de toute façon — la spécification CSS ignore la marge d'un élément `table`. Tout le message se collait aux bords. Elle est passée sur les `<td>`. Un châssis unique remplace les trois mises en page séparées. Le logo entre dans les mails, en image et non en texte, sur un bandeau qui reste sombre dans les deux modes : il n'a ainsi qu'une seule version à exister, juste partout. `public/` n'existait pas, et `output: "standalone"` ne le trace pas — sans la ligne ajoutée au Dockerfile le logo aurait répondu 404 chez tous les destinataires, panne invisible en développement. Les fiches promettaient 24 fois un geste qui n'existe pas — « si c'est déjà fait, dis-le-moi et je me tais », « dis-moi sa date d'expiration et je gère le tempo ». Le pied de page avait déjà été purgé de la même promesse ; le corps la répétait. Retirées sans remplacement : elles reviendront le jour où l'état utilisateur s'écrira. Le bloc d'accompagnement du rappel groupé disparaît aussi — « pour que tu n'aies pas à y penser toi-même » se démentait dans le seul message qui demande justement d'y penser. Et chaque échéance dont la fiche a une page publique porte désormais un lien vers elle : la bicéphalie ne servait que le SEO, alors que c'est au destinataire d'un rappel que l'explication entière manque le plus. Enfin, le lien d'activation. Il n'était pas un raccourci : sa vérification était la seule chose au monde capable de créer le compte de l'acheteur, better-auth ne le créant qu'à ce moment, `resolveMembership` n'inscrivant le premier membre que s'il y a une session, et `destinatairesFoyer` ne connaissant que les appartenances. Un mail lu le soir, un lien ouvert deux fois, un antispam qui le déclenche à vide, et un foyer payé 19,99 € restait sans compte, sans appartenance, donc sans destinataire de rappel, définitivement. Ce n'est pas un cas de bord : ce message n'est envoyé que quand personne n'était connecté avant de payer, donc l'unique mail d'activation qui existe était précisément celui dont l'expiration était fatale. Le webhook crée le compte et l'appartenance lui-même, depuis l'adresse que Stripe vient de collecter. Contrepartie assumée : Stripe ne prouve pas la possession de la boîte, et qui paierait pour l'adresse d'un tiers lui rattacherait un foyer — un ajout indésirable, jamais une prise de contrôle, et facturé à celui qui le tente. `npm run mails:echantillon` rend un exemplaire de chaque message depuis les vraies fiches, et, si une adresse est donnée, le rappel réel de ses foyers. D-048, D-049. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
38 lines
1.6 KiB
TypeScript
38 lines
1.6 KiB
TypeScript
import type { NextConfig } from "next";
|
|
|
|
const nextConfig: NextConfig = {
|
|
output: "standalone",
|
|
serverExternalPackages: ["pg", "yaml", "pg-boss"],
|
|
/**
|
|
* En-têtes de sécurité (audit S6, 28/07/2026) — absents jusqu'ici. Deux conséquences concrètes
|
|
* relevées : le hub encadrable dans une iframe tierce, et l'URL du foyer (une capacité) partant
|
|
* en `Referer` vers chaque lien d'action sortant — c'est-à-dire vers les futurs partenaires
|
|
* d'affiliation (§ 10.4). `rel="noreferrer"` sur ces liens (déjà en place ou ajouté au cas par
|
|
* cas) complète cette politique, il ne la remplace pas : elle doit tenir même sur un lien oublié.
|
|
*/
|
|
async headers() {
|
|
return [
|
|
{
|
|
source: "/:path*",
|
|
headers: [
|
|
{ key: "X-Content-Type-Options", value: "nosniff" },
|
|
{ key: "X-Frame-Options", value: "DENY" },
|
|
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
|
|
{ key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains" },
|
|
],
|
|
},
|
|
/**
|
|
* Le logo des mails est demandé par les mandataires d'images de Gmail, Yahoo et Outlook, une
|
|
* fois par destinataire et par message, sans jamais suivre de hachage de contenu : c'est le
|
|
* seul fichier du produit qu'un tiers rappelle en boucle. Un an de cache, et un nom de
|
|
* fichier qui changera si le dessin change.
|
|
*/
|
|
{
|
|
source: "/email/:path*",
|
|
headers: [{ key: "Cache-Control", value: "public, max-age=31536000, immutable" }],
|
|
},
|
|
];
|
|
},
|
|
};
|
|
|
|
export default nextConfig;
|