Vai al contenuto

Runbook: creare, caricare e schedulare un Campaign Draft

Quando usarlo

Usare questo runbook per affidare al server la creazione del Campaign Draft, l'upload dello snapshot canonico, lo scheduling e la riconciliazione. Il caller esprime una sola intenzione e legge il job; lo scheduling non è prova di delivery.

Prerequisiti

  • Issue published, approved e quality-passing;
  • identità con email:campaigns:schedule;
  • target presente nel catalogo server-owned;
  • provider mode noto;
  • preflight locale npm run preflight:email-configuration verde con canonical code: none, capabilities validated: contacts,campaigns e provider call performed: no;
  • nessun Campaign Draft attivo duplicato;
  • autorizzazione esplicita per le operazioni live, se applicabile.

Procedura

Usare in ordine la cartella email-campaign della collection docs/postman/architecture-coffee.postman_collection.json:

  1. Eseguire il preflight locale con i binding dell'ambiente protetto. Deve risultare local configuration: valid e validare entrambe le capability senza stampare valori, riferimenti o nomi di binding sensibili, chiamare Mailjet o leggere D1. Verificare inoltre che l'Issue sia published, approved, quality-passing e senza campagna attiva in conflitto.
  2. Leggere l'overview o il catalogo target e scegliere soltanto un targetAlias server-owned. Se Mailjet è indisponibile, l'overview conserva l'alias allowlisted ma espone stato unavailable, conteggio ignoto e nessuna azione live; non ricavare l'alias da errori, list ID o memoria della chat.
  3. Impostare un scheduledFor RFC3339 futuro e verificare timezone e istante.
  4. Generare una idempotencyKey bounded e stabile per questa intenzione.
  5. Chiamare una sola volta POST /admin/issues/{slug}/newsletter/schedule con listAlias, at, timezone, confirmation: "schedule" e la chiave. La risposta attesa è 202 con campaignId, jobId e status: requested; non attende Mailjet.
  6. In caso di risposta persa, ripetere soltanto la stessa request con la stessa idempotencyKey: deve tornare lo stesso campaign/job.
  7. Leggere GET /admin/email-campaign-jobs/{jobId} oppure get_email_campaign_job. Non chiamare route ritirate di prepare/upload/schedule.
  8. Attendere completed e poi rileggere campaign e attempt. Se il job diventa operator_required, passare al runbook di recupero senza creare un secondo job o una seconda draft.

Verifiche attese

  • snapshot immutabile e hash persistiti prima del primo provider side effect;
  • Queue payload limitato al solo jobId;
  • attempt distinti per prepare, content_upload, schedule e readback;
  • provider mode corretto;
  • fasi started, response_received e persisted separate, con timestamp e durata provider sanitizzata quando disponibili;
  • provider draft presente solo quando autorevole;
  • aggregate content_uploaded dopo l'upload e scheduled dopo scheduling;
  • lease bounded e nessun job running zombie;
  • schedule ambiguo riconciliato per readback prima di qualunque retry;
  • target esposto solo come alias e riferimento mascherato;
  • scheduling separato dalla delivery e nessun send applicativo.

Condizioni di stop

Fermarsi su Issue non eleggibile, target assente, mismatch di target o timestamp, duplicate active campaign, operator_required, provider mode inatteso, preflight locale non verde o evidenza incompleta. I codici EMAIL_* di configurazione sono locali e non autorizzano una prova provider. Non ripetere ciecamente schedule; riconciliare lo stato provider.

Rollback o recupero

Non cancellare o riscrivere job, snapshot, outbox o attempt terminali. Un dispatch fallito o una lease scaduta sono recuperati dal dispatcher server-side; non ricostruire manualmente la sequenza. Su operator_required usare il recupero Campaign Draft, poi la diagnostica provider se il readback non è univoco.

Evidenza da conservare

  • Issue, target sicuro, campaign ID e job ID;
  • idempotency replay e stato terminale del job;
  • aggregate e attempt;
  • provider mode;
  • stato terminale e codice sanitizzato;
  • parent del retry eventuale per prepare o content_upload;
  • timestamp schedulato e risultato di riconciliazione;
  • conferma che delivery e destinatari sono rimasti fuori scope.