ADR: Remplacement de Sanity par Cloudflare D1 pour les Formulaires et Alertes Email Routing
- Statut : Proposé (En attente de validation)
- Date : 2026-10-29
- Auteur : Antigravity & Architecture AMN
- Projet : AMN (
amn-services,amn-docs,amn-blog) - Dépôts / Composants concernés :
amn-services/submission-processor(Consumer)amn-services/form-handler(Producer)- Base de données Cloudflare D1 (
amn-submissions-dev/amn-submissions-prod) - Cloudflare Email Service / Email Routing (
send_emailbinding)
1. Contexte et Problématique
Section titled “1. Contexte et Problématique”Jusqu’à présent, les soumissions de formulaires (qu’il s’agisse de messages de contact ou d’abonnements newsletter) étaient relayées par Cloudflare Queues vers le Worker submission-processor, qui créait un document d’audit dans le Content Lake de Sanity CMS (_type: 'submission').
Cette approche initiale présente plusieurs limitations majeures :
- Détournement du rôle de Sanity (L1) : Sanity est un CMS headless conçu pour la gestion éditoriale du contenu public (articles, pages, configurations graphiques). L’utiliser comme base de données transactionnelle de formulaires pollue les datasets, consomme inutilement les quotas d’API Sanity (mutations d’écriture), et expose des messages de contact potentiellement confidentiels dans l’interface Sanity Studio.
- Absence de requêtage relationnel natif : Sanity stocke des documents JSON non relationnels. La consultation, le tri, le filtrage et l’exportation des soumissions nécessitent des requêtes GROQ avec pagination complexe au lieu de requêtes SQL standards.
- Absence de notification proactive pour les messages de contact : Lorsqu’un utilisateur soumettait une demande via le formulaire de contact, aucun email d’alerte n’était envoyé à l’équipe. Le message attendait simplement d’être consulté manuellement dans Sanity Studio.
- Coûts et dépendances externes : Réduire les dépendances tierces (en supprimant le client Sanity et ses tokens d’écriture dans le backend L3) renforce la sécurité et l’autonomie de l’infrastructure Cloudflare.
2. Décisions Architecturales
Section titled “2. Décisions Architecturales”2.1. Stockage Transactionnel : Migration intégrale vers Cloudflare D1
Section titled “2.1. Stockage Transactionnel : Migration intégrale vers Cloudflare D1”Nous décidons de remplacer complètement le stockage des soumissions dans Sanity par Cloudflare D1, la base de données relationnelle serverless (SQLite à l’Edge) native de Cloudflare.
- Schéma SQL unifié (
schema.sql) :CREATE TABLE IF NOT EXISTS submissions (id TEXT PRIMARY KEY,type TEXT NOT NULL,name TEXT,email TEXT NOT NULL,message TEXT,source TEXT,submitted_at TEXT NOT NULL); - Binding D1 (
DB) :- Déclaration du binding
env.DB: D1Databasedanssubmission-processor. - Suppression totale de la dépendance
@sanity/clientdupackage.jsonet de tous les imports Sanity. - Suppression définitive des variables et secrets devenus obsolètes :
SANITY_PROJECT_ID,SANITY_DATASETetSANITY_API_WRITE_TOKEN.
- Déclaration du binding
- Écriture SQL préparée :
Toute soumission traitée par le consommateur de queue ou l’endpoint
/test-directest insérée via une requête préparée sécurisée contre les injections SQL :await env.DB.prepare(`INSERT INTO submissions (id, type, name, email, message, source, submitted_at) VALUES (?, ?, ?, ?, ?, ?, ?)`).bind(submission.id,submission.type ?? 'contact',submission.name ?? null,submission.email,submission.message ?? null,submission.source ?? null,submission.submittedAt).run();
2.2. Alertes Immédiates : Cloudflare Email Routing & Service d’Envoi Natif
Section titled “2.2. Alertes Immédiates : Cloudflare Email Routing & Service d’Envoi Natif”Pour les soumissions de type contact, une alerte par email doit être immédiatement acheminée vers la boîte de réception de l’organisation.
- Binding Natif
send_email: Nous configurons le binding d’envoi d’email Cloudflare (send_email) dans la configuration Wrangler :"send_email": [{"name": "EMAIL"}] - Variables d’environnement dédiées (non codées en dur) :
NOTIFICATION_EMAIL_TO: Adresse de destination de l’entreprise (ex:contact@audyssee.orgou variable d’environnement).NOTIFICATION_EMAIL_FROM: Adresse d’expédition validée sur le domaine Email Routing Cloudflare (ex:hello@amn-mail-dev.lasommedesrosees.com).
- Composition & Envoi MIME :
Dès l’insertion réussie dans D1, si
submission.type === 'contact', le Worker construit un message bilingue/multiformat (texte brut + HTML épuré) et l’expédie viaenv.EMAIL.send(emailMessage). En cas de problème d’envoi d’email, l’erreur est loguée sans bloquer l’accusé de réception (ACK) de la Queue si l’insertion D1 a déjà réussi, ou fait l’objet d’un retry contrôlé.
2.3. Préservation de la Logique Loops (Newsletter)
Section titled “2.3. Préservation de la Logique Loops (Newsletter)”- La fonction
registerLoopsContactest strictement préservée. - Pour les soumissions de type
newsletter:- La soumission est enregistrée dans la base D1 (
submissions). - Le contact est transmis de manière asynchrone à Loops (via le Form Endpoint Double Opt-in ou l’API REST avec gestion de la liste de diffusion et de l’idempotence HTTP 409).
- La soumission est enregistrée dans la base D1 (
- Pour les soumissions de type
contact:- La soumission est enregistrée dans la base D1 (
submissions). - L’alerte email est expédiée à
NOTIFICATION_EMAIL_TOvia Cloudflare Email Routing. - Aucun appel vers Loops n’est effectué (sauf si l’utilisateur coche expressément l’inscription à la newsletter dans une future évolution).
- La soumission est enregistrée dans la base D1 (
2.4. Alignement du Worker form-handler (Producer)
Section titled “2.4. Alignement du Worker form-handler (Producer)”- Le rôle de
form-handlerreste inchangé : validation Zod en amont (contactetnewsletter) et injection dansSUBMISSIONS_QUEUE. - Son interface
Envreste minimaliste et isolée (SUBMISSIONS_QUEUE: Queue,ENVIRONMENT: string).
3. Justification des Choix Techniques
Section titled “3. Justification des Choix Techniques”| Critère | Ancienne approche (Sanity) | Nouvelle approche (Cloudflare D1 + Email Routing) |
|---|---|---|
| Rôle architectural | Détournement d’un CMS éditorial L1 | Base de données SQL native L3 dédiée aux données applicatives |
| Latence & Réseau | Requête HTTPS externe vers l’API Sanity | Requête SQL locale interne sur l’Edge Cloudflare (< 10ms) |
| Limites & Quotas | Consomme les quotas d’API mutations Sanity | Inclus dans le runtime Cloudflare Workers D1 (100k lectures / 50k écritures/jour en Free) |
| Sécurité des données | Données de contact visibles dans le CMS | Données isolées en base relationnelle privée, accessibles uniquement aux services L3 |
| Notification de contact | Aucune (polling manuel requis dans Sanity) | Envoi temps réel sur la messagerie de l’organisation via Email Routing |
| Maintenance des secrets | 3 secrets Sanity à maintenir (PROJECT_ID, DATASET, TOKEN) |
0 secret Sanity nécessaire dans la couche L3 |
4. Conséquences de la Décision
Section titled “4. Conséquences de la Décision”4.1. Impacts Positifs
Section titled “4.1. Impacts Positifs”- Allègement des Workers : Suppression de
@sanity/clientet de ses dépendances volumineuses, réduisant la taille du bundle desubmission-processorde plus de 60%. - Autonomie de l’écosystème Cloudflare : Pipeline 100% exécuté au sein de Cloudflare (Worker Producer -> Queue -> Worker Consumer -> D1 + Email Routing).
- Réactivité opérationnelle : Tout message envoyé depuis la page Contact déclenche une notification par email instantanée pour l’équipe.
- Historisation fiable : Toutes les soumissions (contact et newsletter) disposent d’un audit SQL pérenne, facilement requêtable par date, email ou source.
4.2. Points d’Attention & Actions
Section titled “4.2. Points d’Attention & Actions”- Initialisation de la base D1 : Il est nécessaire d’exécuter
schema.sqlsur la base locale (--local) et sur la base distante Cloudflare (--remote). - Validation du domaine expéditeur : L’adresse
NOTIFICATION_EMAIL_FROMdoit appartenir à un domaine activé sur Cloudflare Email Routing / Email Sending. - Migration des anciennes données : Les anciennes soumissions archivées dans Sanity restent consultables dans Sanity Studio en lecture seule sans impacter le nouveau système.