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-configurationverde concanonical code: none,capabilities validated: contacts,campaignseprovider 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:
- Eseguire il preflight locale con i binding dell'ambiente protetto. Deve
risultare
local configuration: valide 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. - Leggere l'overview o il catalogo target e scegliere soltanto un
targetAliasserver-owned. Se Mailjet è indisponibile, l'overview conserva l'alias allowlisted ma espone statounavailable, conteggio ignoto e nessuna azione live; non ricavare l'alias da errori, list ID o memoria della chat. - Impostare un
scheduledForRFC3339 futuro e verificare timezone e istante. - Generare una
idempotencyKeybounded e stabile per questa intenzione. - Chiamare una sola volta
POST /admin/issues/{slug}/newsletter/scheduleconlistAlias,at,timezone,confirmation: "schedule"e la chiave. La risposta attesa è202concampaignId,jobIdestatus: requested; non attende Mailjet. - In caso di risposta persa, ripetere soltanto la stessa request con la stessa
idempotencyKey: deve tornare lo stesso campaign/job. - Leggere
GET /admin/email-campaign-jobs/{jobId}oppureget_email_campaign_job. Non chiamare route ritirate di prepare/upload/schedule. - Attendere
completede poi rileggere campaign e attempt. Se il job diventaoperator_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,schedulee readback; - provider mode corretto;
- fasi
started,response_receivedepersistedseparate, con timestamp e durata provider sanitizzata quando disponibili; - provider draft presente solo quando autorevole;
- aggregate
content_uploadeddopo l'upload escheduleddopo scheduling; - lease bounded e nessun job
runningzombie; scheduleambiguo 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
prepareocontent_upload; - timestamp schedulato e risultato di riconciliazione;
- conferma che delivery e destinatari sono rimasti fuori scope.