Skip to content

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" et aria-live="polite".
    • Gestion du focus et du statut disabled pendant la requête pour prévenir la double soumission.
  • Sanity Stega Clean :
    • Tous les attributs HTML (placeholder, data-sanity) utilisent stegaClean() pour éviter toute interférence de métadonnées visuelles invisibles dans les requêtes réseau.
  • 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-direct ou http://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 }).
  • Archivage Sanity :
    • Document submission enrichi avec formType: "newsletter", email: "...", status: "processed".
    • Ne bloque pas la confirmation si Loops a validé le contact.

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').
  • name et message rendus optionnels si formType === 'newsletter'.

4. Configuration du Serveur MCP Loops

4.1. Analyse du Protocole & Résolution d’Accès

  • L’URL officielle https://mcp.loops.so est un serveur distant exigeant un flux d’autorisation interactive OAuth 2.0 PKCE via le portail https://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 796e3ae3c6da58a59e14787256ce0507 dans Antigravity IDE / Cursor est d’utiliser le runner stdio loops-mcp-server via npx.

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 dans wrangler.json, car ce fichier est versionné publiquement sur GitHub. Elles doivent être gérées exclusivement via :

  1. .dev.vars (ignoré par git) pour le développement local.
  2. wrangler secret put <NAME> --env <env> pour les Workers Cloudflare.
  3. 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

  1. 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

  1. 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})$.
  2. Observabilité :
    • Chaque soumission reçoit un UUID unique (id: crypto.randomUUID()) logué à chaque étape du pipeline pour le suivi dans Cloudflare Logpush et wrangler tail.
Switch to normal mode

Contents