DAT: Spécifications Techniques Newsletter Loops, Queues & Sanity
DAT: Spécifications Techniques Newsletter Loops, Queues & Sanity
Dossier d'Architecture Technique détaillé pour l'implémentation de la newsletter du prefooter : flux de données, schémas Zod/Sanity, Worker Producer/Consumer et MCP Loops.
Dossier d’Architecture Technique (DAT) — Newsletter Loops & Pipeline L1/L2/L3
- Composant : Formulaire Newsletter Prefooter & Service d’Abonnement
- Date : 2026-10-26
- Statut : En attente de validation
- Couches impliquées : L1 (Sanity Studio), L2 (Astro Presentation), L3 (Cloudflare Workers & Queues), Tiers (Loops.so)
1. Schéma d’Architecture Globale1/2
1. Schéma d’Architecture Globale2/2
┌────────────────────────────────────────────────────────────────────────┐│ COUCHE L1 : SANITY CMS ││ • prefooterSettings (textes, boutons, feedback succès/erreur) ││ • submission (piste d'audit des inscrits newsletter & messages) │└───────────────────────────────────┬────────────────────────────────────┘ │ (GROQ SSR Build / Runtime) ▼┌────────────────────────────────────────────────────────────────────────┐│ COUCHE L2 : ASTRO PRESENTATION ││ • PreFooter.astro (Formulaire accessible, Stega clean, Micro-états) ││ • /api/submit.ts (Point d'entrée Edge Astro SSR) │└───────────────────────────────────┬────────────────────────────────────┘ │ (Service Binding direct / Local HTTP) ▼┌────────────────────────────────────────────────────────────────────────┐│ COUCHE L3 : CLOUDFLARE SERVICES ││ ││ [ form-handler ] ──(Zod validation)──> [ submissions-queue ] ││ (Producer) (Queue Edge) ││ │ ││ ▼ ││ [ submission-processor ] ││ (Consumer) │└───────────────────────────────────────┬──────────────────────┬─────────┘ │ │ (POST Form DOI / REST API) │ │ (SQL INSERT INTO) ▼ ▼┌───────────────────────────────────────────────┐ ┌──────────────────────┐│ LOOPS.SO (Emailing CRM) │ │ CLOUDFLARE D1 (SQL) ││ • Audience Contact / Form DOI │ │ Table: submissions ││ • MailingList : env.LOOPS_MAILING_LIST_ID │ │ • id, type, email ││ • UserGroup : env.LOOPS_USER_GROUP │ │ • submitted_at ││ • Source : env.LOOPS_DEFAULT_SOURCE │ │ • source, message │└───────────────────────────────────────────────┘ └──────────────────────┘2. Flux Séquentiel de Données
sequenceDiagram autonumber actor User as Visiteur (Navigateur) participant UI as PreFooter.astro (L2) participant Edge as Astro /api/submit (L2) participant Producer as Worker form-handler (L3) participant Queue as Cloudflare Queue (L3) participant Consumer as Worker submission-processor (L3) participant Loops as Loops.so (Form DOI / API) participant D1 as Cloudflare D1 (Table submissions)
User->>UI: Saisie email + Clic "S'abonner" UI->>UI: Validation HTML5 + Passage bouton en état "Subscribing..." UI->>Edge: POST /api/submit { type: "newsletter", email: "user@example.com" } Edge->>Producer: Appel via Service Binding FORM_HANDLER Producer->>Producer: Validation Zod (NewsletterSubmissionSchema) Producer->>Queue: send({ id, type, email, submittedAt, environment }) Producer-->>Edge: HTTP 200 { success: true, id: "..." } (< 40ms) Edge-->>UI: HTTP 200 { success: true } UI->>UI: Affichage message de confirmation + reset formulaire
Note over Queue,Consumer: Traitement asynchrone par lot (Batching) Queue->>Consumer: Réception du lot de messages Consumer->>Loops: POST /api/newsletter-form/{formId} ou /api/v1/contacts/create Loops-->>Consumer: HTTP 200 (Contact créé / Double Opt-In enclenché) Consumer->>D1: INSERT INTO submissions (id, type, email, source, submitted_at) D1-->>Consumer: Confirmation écriture SQL Consumer->>Queue: message.ack() (Accusé de réception)3. Spécifications Détaillées des Composants
3.1. Couche L2 : Présentation (amn-blog)
Composant PreFooter.astro
- Accessibilité (A11y) :
- Champ email avec
<label for="newsletter-email" class="sr-only">. - Zone de feedback dynamique avec attributs
role="status"etaria-live="polite". - Gestion du focus et du statut
disabledpendant la requête pour prévenir la double soumission.
- Champ email avec
- Sanity Stega Clean :
- Tous les attributs HTML (
placeholder,data-sanity) utilisentstegaClean()pour éviter toute interférence de métadonnées visuelles invisibles dans les requêtes réseau.
- Tous les attributs HTML (
- Format du Payload soumis :
{"type": "newsletter","email": "visiteur@domaine.com","source": "amn-blog prefooter"}
Point d’entrée /api/submit.ts
- En environnement de production (Cloudflare Pages/Workers SSR), accède au binding
FORM_HANDLER.fetch('http://internal/api/submit', ...)sans passer par le réseau public. - En local, relaie vers le port local
http://localhost:8789/test-directouhttp://localhost:8787/api/submit.
3.2. Couche L3 : Services Cloudflare (amn-services)
1. Worker form-handler (Producer)
Mise à jour du validateur pour supporter une union discriminée afin de servir à la fois le formulaire de contact et la newsletter :
import { z } from 'zod';
const ContactSubmissionSchema = z.object({ type: z.literal('contact').default('contact'), name: z.string().min(2).max(100), email: z.string().email(), message: z.string().min(10).max(2000),});
const NewsletterSubmissionSchema = z.object({ type: z.literal('newsletter'), email: z.string().email(), source: z.string().optional().default('prefooter'),});
export const SubmissionPayloadSchema = z.discriminatedUnion('type', [ ContactSubmissionSchema, NewsletterSubmissionSchema,]).or(ContactSubmissionSchema); // Rétrocompatibilité avec les formulaires n'envoyant pas 'type'2. Worker submission-processor (Consumer)
Consommateur de SUBMISSIONS_QUEUE doté de la double intégration :
- Appel Loops REST API :
- Endpoint :
POST https://app.loops.so/api/v1/contacts/create - Headers :
Authorization: Bearer ${env.LOOPS_API_KEY}Content-Type: application/json
- Corps de la requête :
{"email": "visiteur@domaine.com","source": "amn-blog prefooter","subscribed": true,"userGroup": "Newsletter"}
- Gestion des statuts Loops :
- HTTP 200 / 201 : Contact créé ou mis à jour avec succès.
- HTTP 409 / contact déjà existant : Considéré comme succès (idempotence).
- HTTP 429 (Rate limit) :
message.retry({ delaySeconds: 15 }).
- Endpoint :
- Archivage Sanity :
- Document
submissionenrichi avecformType: "newsletter",email: "...",status: "processed". - Ne bloque pas la confirmation si Loops a validé le contact.
- Document
3.3. Couche L1 : Schémas Sanity Studio (amn-studio)
Schéma prefooterSettings.ts
Ajout des champs de messages personnalisables :
newsletterSuccessMessage(string, initialValue:"Thank you for subscribing!")newsletterErrorMessage(string, initialValue:"An error occurred. Please try again.")
Schéma submission.ts
Adaptation des contraintes de validation conditionnelles :
formType(string :'contact'ou'newsletter', défaut'contact').nameetmessagerendus optionnels siformType === 'newsletter'.
4. Configuration du Serveur MCP Loops
4.1. Analyse du Protocole & Résolution d’Accès
- L’URL officielle
https://mcp.loops.soest un serveur distant exigeant un flux d’autorisation interactive OAuth 2.0 PKCE via le portailhttps://app.loops.so. L’envoi direct d’une clé API en Bearer token est rejeté par Cloudflare (loops rejected token: invalid token). - La solution standardisée pour injecter la clé API
796e3ae3c6da58a59e14787256ce0507dans Antigravity IDE / Cursor est d’utiliser le runner stdioloops-mcp-servervianpx.
4.2. Configuration dans ~/.gemini/config/mcp_config.json
{ "mcpServers": { "loops": { "command": "npx", "args": ["-y", "loops-mcp-server"], "env": { "LOOPS_API_KEY": "796e3ae3c6da58a59e14787256ce0507" } } }}4.3. Outils MCP Exposés
loops_create_contact: Enregistrement ou mise à jour directe d’un abonné.loops_find_contact: Recherche par email pour vérification du statut d’abonnement.loops_update_contact: Modification des propriétés personnalisées d’un contact.loops_send_event: Déclenchement d’un événement automatisé (ex:welcome_email).loops_list_mailing_lists: Listing des listes de diffusion actives sur Loops.
5. Gestion des Environnements, Secrets et Variables Loops1/3
[!CAUTION] Règle de Sécurité Impérative : Les variables contenant des identifiants (
LOOPS_FORM_ID,LOOPS_MAILING_LIST_ID) ou des clés (LOOPS_API_KEY,SANITY_API_WRITE_TOKEN) ne doivent JAMAIS être déclarées en clair danswrangler.json, car ce fichier est versionné publiquement sur GitHub. Elles doivent être gérées exclusivement via :
.dev.vars(ignoré par git) pour le développement local.wrangler secret put <NAME> --env <env>pour les Workers Cloudflare.- GitHub Actions Secrets (
secrets.<NAME>) injectés automatiquement lors des déploiements CI/CD.
5. Gestion des Environnements, Secrets et Variables Loops2/3
| Variable / Secret | Type | Composant | Environnements | Description & Rôle |
|---|---|---|---|---|
LOOPS_API_KEY |
Secret | submission-processor |
dev, production |
Clé d’API secrète Loops pour les opérations REST (Single Opt-In, lookup, update). |
LOOPS_FORM_ID |
Secret / ID | submission-processor |
dev, production |
Identifiant du formulaire Loops (permet de router vers l’endpoint natif Double Opt-In). |
LOOPS_MAILING_LIST_ID |
Secret / ID | submission-processor |
dev, production |
Identifiant de la Mailing List ciblée (ex: liste amn dev en dev, liste principale en prod). La liste doit être configurée sur Public dans Loops pour être acceptée par le formulaire. |
LOOPS_USER_GROUP |
Optionnel | submission-processor |
dev, production |
Groupe d’utilisateurs assigné au contact dans Loops (par défaut : 'Newsletter'). |
LOOPS_SOURCE |
Optionnel | submission-processor |
dev, production |
Tag de provenance du contact (par défaut : 'amn-blog prefooter'). |
DB |
Binding D1 | submission-processor |
Cloudflare D1 | Base de données SQL serverless pour l’enregistrement persistant des soumissions (remplace Sanity). |
5. Gestion des Environnements, Secrets et Variables Loops3/3
| Variable / Secret | Type | Composant | Environnements | Description & Rôle |
|---|---|---|---|---|
EMAIL |
Binding Natif | submission-processor |
Cloudflare Email Service | Binding send_email pour l’envoi d’alertes par email lors d’une soumission de contact. |
NOTIFICATION_EMAIL_TO |
Secret / Var | submission-processor |
dev, production |
Adresse de réception des alertes de contact (ex: contact@audyssee.org). |
NOTIFICATION_EMAIL_FROM |
Secret / Var | submission-processor |
dev, production |
Adresse d’expédition validée sur le domaine Email Routing Cloudflare. |
SUBMISSIONS_QUEUE |
Binding | form-handler & submission-processor |
Cloudflare Queues | File de messages asynchrone découplant la réception du traitement D1/Loops. |
FORM_HANDLER |
Service Binding | amn-blog (SSR) |
Cloudflare Edge | Liaison directe entre le frontend Astro SSR et le Producer Worker. |
6. Observabilité, Tolérance aux Pannes & Sécurité1/2
- Idempotence & Anti-rebond :
- Si un utilisateur clique plusieurs fois, le frontend désactive le bouton immédiatement.
- L’API Loops met à jour le contact existant sans créer de doublon.
6. Observabilité, Tolérance aux Pannes & Sécurité2/2
- Retry Exponentiel :
- En cas de saturation d’API (Loops rate-limit 10 req/s), le consommateur Cloudflare réessaye le message avec un délai progressif : $delay = \min(60, 2^{attempt})$.
- Observabilité :
- Chaque soumission reçoit un UUID unique (
id: crypto.randomUUID()) logué à chaque étape du pipeline pour le suivi dans Cloudflare Logpush etwrangler tail.
- Chaque soumission reçoit un UUID unique (